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.
lib/uploads.ts
uploadHandler(options) returns { GET, POST }, a pair of route handlers.
Mounting it
app/api/upload/route.ts
lib/upload-hooks.ts
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.
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
lib/uploads.ts
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
lib/uploads.ts
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
lib/uploads.ts
What it returns:
What each field does is on Authorizing an upload. Throw a
BlobError to refuse:
lib/uploads.ts
Write onBeforeUpload above onUploadComplete
Write onBeforeUpload above onUploadComplete
lib/uploads.ts
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
lib/uploads.ts
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>:
app/page.tsx
onError
lib/uploads.ts
Return a
BlobError or a Response to answer with it, or nothing to leave the answer alone. See Handling errors.
context
lib/uploads.ts
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
lib/uploads.ts
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:lib/callbacks.ts
Routes
lib/uploads.ts
app/page.tsx
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()
lib/uploads.ts
app/page.tsx
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.
input is validated and what state exposes are on Input and state.