File uploads are deceptively complex. A small file via multipart form is straightforward. A 10 GB video, a flaky mobile connection, a multi-tenant system — each adds requirements. The right pattern depends on file size, reliability needs, and infrastructure.
This page covers the patterns that scale.
The classic browser pattern:
POST /api/upload
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="report.pdf"
Content-Type: application/pdf
(binary data)
------WebKitFormBoundary--
Server parses the multipart envelope, extracts the file, processes it.
When to use: small files (under ~10 MB), stable connections, simple use cases. Browser default for <form enctype="multipart/form-data">.
Limitations: full file in one request; failure means starting over; ties up application server during upload; server must handle untrusted content.
The application server generates a time-limited URL for direct upload to object storage (S3, GCS, Azure Blob). The client uploads directly to storage; the application server is bypassed for the bulk transfer.
Client → POST /api/upload-init → Server
(returns: { signed_url: "https://s3...", file_id: "abc" })
Client → PUT https://s3... (binary data) → S3
Client → POST /api/upload-complete?file_id=abc → Server
Advantages:
When to use: most production file uploads. Default for files larger than a few MB.
For large files or unreliable networks, resumable protocols allow continuing an interrupted upload.
The client splits the file into parts, uploads each separately, and signals completion. Failed parts can be re-uploaded; the client tracks which parts succeeded.
1. POST initiate-multipart-upload → upload_id
2. PUT part 1 with upload_id and part number
3. PUT part 2 with upload_id and part number
... (parts can be parallel)
4. POST complete-multipart-upload → final object
S3 multipart uploads support parts up to 5 GB; a single upload can be terabytes.
Open protocol for resumable uploads. The client tracks the offset; the server reports how much has been received. Either side can resume after interruption.
Used by Vimeo, Cloudflare, others. Less common than S3 multipart in cloud-native stacks.
Browser-native streaming via fetch with a ReadableStream body. Less standardized; works with HTTP/2 well.
Don't trust uploaded files. Always:
After upload:
filename=../../etc/passwd; server writes outside intended directory. Always sanitize filenames.image/png but is HTML; browser interprets it as HTML and runs scripts. Use Content-Disposition or strict MIME enforcement.