@upstash/blob/react is the browser side of a direct upload. uploadHooks binds the hooks to your upload handler, and useUpload runs the upload and reports its progress. There is a plain function for apps without React, and useServerUpload for routes where the bytes do pass through your app.
lib/upload-hooks.ts
app/page.tsx
uploadHooks
lib/upload-hooks.ts
uploadHooks<typeof uploads>(defaults) binds useUpload to one handler. The bound hook knows the route names, so a typo does not compile, and it knows each route’s input and completion data. Called with no type parameter, uploadHooks() returns an unbound useUpload that takes a URL instead of a route name.
Every option is optional.
A call-site option on
useUpload wins over the default, except onError, where the default runs first and the call-site one after it.
useUpload
app/page.tsx
app/page.tsx
start({ file }) returns one record, or null when the file is nullish, so an empty file picker is not an error. start({ files }) takes a File[] or a FileList and returns an array.
Three files upload at once by default and the rest queue. clear(id?) removes records from the list; a cleared upload that is still running finishes anyway. Unmounting the component does not cancel anything either.
The record
Drive UI off
pending rather than deriving it from status. percent stays at 99 through finishing, because 100 means stored, not sent. See Progress and status.
blob.data is typed from that route’s onUploadComplete. Fields a status does not carry are undefined rather than absent, so upload?.blob?.url and upload?.error?.message read straight off the record with no narrowing.
canPause is false for a single PUT, which is every file under the multipart threshold. retry() works only from error, and resumes from the parts that already landed. See Large files.
headers
app/page.tsx
headers is a function, not an object. It is called before every request the SDK makes to your route, so a token that rotates mid-upload keeps working.
A throw from it fails the upload with that error and no retry. Use this to refuse an upload from the app side, for example when a token could not be refreshed.
Without React
app/uploader.ts
upload() starts immediately and returns an UploadTask: snapshot() for the current state, subscribe() for changes, done as a promise, and pause(), resume(), cancel() and retry(). The snapshot has the same fields as the React record.
useServerUpload
app/api/avatar/route.ts
app/avatar.tsx
bucket.put, and drive it with useServerUpload: one POST, with upload progress, cancellation and BlobError decoding. The route’s JSON comes back as response.
Every option is optional.
start({ body }) sends a File, Blob or FormData as the raw body instead of a form field. The record has cancel() only and no pause, since there is no multipart. Statuses are queued, uploading, finishing, done, canceled and error.
A proxied upload is capped by your platform’s request body limit rather than by maxSize. The SDK surfaces that refusal as too_large with the platform’s limit as a hint; see Platform body limits. Anything larger needs a direct upload with an upload handler.