Advanced usage

Headless usage, events, concurrency, transforms and the render prop.

Headless engine

The engine runs anywhere File exists, vanilla JS, Svelte, a web worker:

import { createUploader, s3Adapter } from "@uploadcn/core"

const uploader = createUploader({ adapter: s3Adapter({ endpoint: "/api/upload" }) })
const stop = uploader.mount() // listen for online/offline

uploader.subscribe(() => render(uploader.getState().items))
input.addEventListener("change", () => uploader.add(input.files!))

The render prop

Every primitive can render a different element, like Base UI:

<UploadPrimitive.Trigger render={<Button variant="secondary" />}>Browse</UploadPrimitive.Trigger>

<UploadPrimitive.Progress
  render={(props, { percent }) => <CircularProgress {...props} value={percent} />}
/>

Event handlers are merged; call event.preventDefault() in yours to skip the built-in behavior.

Concurrency and retries

createUploader({
  adapter,
  concurrency: 4,
  retry: { retries: 5, baseDelay: 1000, maxDelay: 30_000, factor: 2, jitter: true },
})

Delays use exponential backoff with equal jitter, and never undercut a server's Retry-After.

Transforms

transform runs after validation and before queueing. Return a File or Blob:

transform: async (file) => (file.type === "image/heic" ? convertHeic(file) : file)

item.originalFile keeps what the user selected; item.file is what gets uploaded.

Metadata

await uploader.add(files, { meta: { folder: "invoices" } })

s3Adapter forwards meta to your route (getKey, onUploadComplete).

Selective re-rendering

Lists re-render only when items are added or removed; each item re-renders only for its own changes. Use useUploadSelector to subscribe to exactly what a component needs.

Localization

<Upload messages={{ status: { uploading: "Wird hochgeladen" }, uploaded: (name) => `${name} hochgeladen` }} />

On this page