Skip to main content
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
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

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
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

lib/uploads.ts
What it returns: What each field does is on Authorizing an upload. Throw a BlobError to refuse:
lib/uploads.ts
lib/uploads.ts
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

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.
lib/uploads.ts
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:
lib/callbacks.ts

Routes

lib/uploads.ts
app/page.tsx
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()

lib/uploads.ts
app/page.tsx
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.
  • input takes any Standard Schema, such as zod. Only the parsed value reaches onBeforeUpload.
  • 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.