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
- 1Select and validate
- 2presign { name, type, size }auth · limits · key
- 3Signed PUT URL
- 4PUT byteswith progress, straight to storage
- 5complete { key }HEAD to verify · save metadata
- 6{ key, url, data }
What the server enforces
Client-side validation is a convenience; the route is the source of truth.
- Authentication and authorization:
authorizeruns before every action. - Size:
maxFileSizeis checked before signing, and the signature locks theContent-Length, so S3 rejects a larger body. - Type:
allowedTypesis checked before signing, and the signature locks theContent-Type. - Existence:
completeverifies the object withHEADbefore callingonUploadComplete, 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 PUT | Multipart | |
|---|---|---|
| Max size | 5 GB (S3) | 5 TB |
| Pause / resume | Restarts from 0 | Continues from the last part |
| Parallelism | One stream | Several parts at once |
| Requests | 3 | 2 + parts × 2 |
s3Adapter picks automatically with multipart.threshold (64 MB by default).
Next.js
npx shadcn@latest add @uploadcn/upload-routeSee Amazon S3 and Cloudflare R2 for the full setup, and TanStack Start for other frameworks.