Presigned uploads

Let the browser upload straight to storage while your server stays in control.

With presigned uploads, your server never handles file bytes. It only signs requests, which keeps it fast, cheap, and free of body-size limits.

BrowserYour routeBucket
  1. 1Select and validate
  2. 2presign { name, type, size }auth · limits · key
  3. 3Signed PUT URL
  4. 4PUT byteswith progress, straight to storage
  5. 5complete { key }HEAD to verify · save metadata
  6. 6{ key, url, data }

What the server enforces

Client-side validation is a convenience; the route is the source of truth.

  • Authentication and authorization: authorize runs before every action.
  • Size: maxFileSize is checked before signing, and the signature locks the Content-Length, so S3 rejects a larger body.
  • Type: allowedTypes is checked before signing, and the signature locks the Content-Type.
  • Existence: complete verifies the object with HEAD before calling onUploadComplete, so clients can't register files they never uploaded.
  • Keys: generated on the server (getKey), never trusted from the client.

Single PUT or multipart?

Single PUTMultipart
Max size5 GB (S3)5 TB
Pause / resumeRestarts from 0Continues from the last part
ParallelismOne streamSeveral parts at once
Requests32 + parts × 2

s3Adapter picks automatically with multipart.threshold (64 MB by default).

Next.js

npx shadcn@latest add @uploadcn/upload-route

See Amazon S3 and Cloudflare R2 for the full setup, and TanStack Start for other frameworks.

On this page