uploadHandler is the TypeScript server side of a direct browser upload. What the handler does, the order its callbacks run in, and their guarantees are on Upload handler. This page has the TypeScript names, types and framework wiring.
uploadHandler(options) returns { GET, POST }, a pair of route handlers.
Mounting it#
That is Next.js App Router. Where the client looks for the handler is on Mounting it; endpoint on uploadHooks or useUpload changes it. uploadHooks<typeof uploads> binds the client to the handler’s type, so route names and completion data are checked at compile time. The import type is erased at build time and never pulls server code into the browser bundle. Upload client has the hooks in full.
Other frameworks#
GET and POST are plain (request: Request) => Promise<Response> functions, so any framework that hands you a fetch Request can mount them.
Frameworks built on Node’s http module, such as Express, need an adapter that converts the incoming request into a fetch Request first.
On Cloudflare Workers the token only exists on the request’s env, so build the bucket from env and pass it as bucket; see Cloudflare Workers.
Options#
| Option | Type | Description |
|---|---|---|
bucket | Bucket | The bucket every route writes to. See The bucket. |
constraints | { contentTypes?, maxSize? } | What the route accepts. See Constraints. |
multipart | boolean | Size | Where an upload stops being one PUT. See Large files. |
endpoint | string | Where the handler is mounted. Only needed to separate two handlers on one bucket. |
context | (request: Request) => TCtx | Runs once per POST. Its value is ctx in every callback. Without it, ctx is undefined. See Context. |
input | Standard Schema | Validates what the browser sends as input before onBeforeUpload runs. Every route inherits it; a route declares its own with uploadRoute(). |
onBeforeUpload | (args) => { path, ... } | Required, here or on every route. See onBeforeUpload. |
onUploadComplete | (args) => TData | What it returns becomes upload.blob.data. See onUploadComplete. |
onError | (args) => BlobError | Response | void | Sees every refusal. See onError. |
routes | Record<string, route> | Mounts several routes at this one endpoint. See Routes. |
Everything except routes, endpoint and context is a default each route inherits; see Multiple routes. Bad options throw a TypeError at startup, where they are written: an unparseable multipart size, an invalid route name, an empty routes map, a missing token.
The bucket#
What the handler uses with no bucket is on Handler options. Pass bucket: when the token lives under another variable, the bucket needs cache, or you are on Cloudflare Workers.
onBeforeUpload#
| Argument | Type | Description |
|---|---|---|
ctx | TCtx | Whatever context returned for this request. |
route | string | The route this file was sent to. '' when the handler mounts no named routes. |
request | Request | The begin request, headers and cookies intact. |
file | { name, type, size } | What the browser declared, before a byte was sent. |
input | TInput | The validated input, when the route declares a schema. |
What it returns:
| Field | Type | Description |
|---|---|---|
path | string | Required. Where the object is stored. |
cache | CacheOption | The Cache-Control this object is stored with. See Caching. |
metadata | Record<string, string> | Signed into the upload and handed back to onUploadComplete. |
constraints | { contentTypes?, maxSize? } | Narrows this one upload’s limits. |
state | TState | Anything computed here that onUploadComplete and onError should get without a lookup. Typed from what this callback returns in a handler without routes, written above onUploadComplete. With routes, give each route that needs it an uploadRoute(). |
What each field does is on Authorizing an upload. Throw a BlobError to refuse:
Write onBeforeUpload above onUploadComplete
In a handler without routes, write onBeforeUpload above onUploadComplete, or annotate its parameter. When onBeforeUpload is written below with an unannotated parameter, TypeScript fixes state to undefined first and reports the error on onBeforeUpload: { name: string } is not assignable to undefined.
onUploadComplete#
| Argument | Type | Description |
|---|---|---|
path | string | Where the object is stored. |
url | string | undefined | The public URL. undefined on a private bucket. |
versionedUrl | string | undefined | url with the etag on the query. |
size | number | Bytes actually stored. |
etag | string | The stored object’s etag. |
uploadedAt | Date | When storage wrote it. |
contentType | string | What the object is stored as. |
ctx | TCtx | What context returned for this request. |
route | string | The route name, '' for a sole route. |
request | Request | The end request. |
file | { name, type, size } | What the browser declared at begin. |
uploadId | string | Identifies this upload. Stable across retries. |
multipartUploadId | string | undefined | For bucket.abortMultipartUpload(). undefined for a single PUT. |
metadata | Record<string, string> | What onBeforeUpload returned, minus the SDK’s marker. |
state | TState | What onBeforeUpload returned as state. |
What each field means, and why it may run more than once, is on Recording the upload.
What it returns is handed to the browser as upload.blob.data, typed through uploadHooks<typeof uploads>:
onError#
| Argument | Type | Description |
|---|---|---|
ctx | TCtx | undefined | undefined when context itself threw, or when no route matched. |
route | string | The name from the query, even when nothing mounts it. |
request | Request | |
error | unknown | Whatever was thrown. |
file, path, metadata, state | optional | As much as the request had reached before it failed. |
Return a BlobError or a Response to answer with it, or nothing to leave the answer alone. See Handling errors.
context#
context takes the POST’s Request. Its awaited value is ctx in every callback, and typed there. When it runs is on Context.
Write context above routes
Write context above routes, or annotate its parameter as (request: Request) =>. Written below routes with an unannotated parameter, TypeScript infers ctx: undefined for the routes first and reports the error on context: Promise<Session> is not assignable to undefined.
Callbacks in another file#
For a callback written in another file, annotate the argument with the helper types, which take the handler and produce the right shape:
Routes#
A route is an object with any of bucket, constraints, multipart, onBeforeUpload, onUploadComplete and onError. A key it names overrides the handler’s. A handler with no routes is reached by a bound useUpload() that takes no argument. The rules for names and inheritance are on Multiple routes.
uploadRoute()#
Use uploadRoute() when a route needs an input schema for data the browser sends along with the file, or a typed state passed from onBeforeUpload to onUploadComplete. A plain route object cannot type either. A handler without routes types state from its own onBeforeUpload, written above its onUploadComplete; with routes, each route that needs it takes an uploadRoute(). It is curried so the ctx type can be named, and it is written outside the routes map.
inputtakes any Standard Schema, such as zod. Only the parsed value reachesonBeforeUpload.- Everything else on the route works as on a plain object:
bucket,constraints,multipart,onError, and the same inheritance from the handler.
How input is validated and what state exposes are on Input and state.