What is multipart/form-data Content-Type? File Uploads, Examples
multipart/form-data is the HTTP content-type for forms with file uploads — splits each field into a separate part with its own headers and body.
What is multipart/form-data?
multipart/form-data is an HTTP request Content-Type used to submit form data that contains files or binary content alongside text fields. Unlike application/x-www-form-urlencoded (the default form encoding for text-only fields), multipart/form-data sends each form field as a separate "part" in the request body, with its own headers and content. This structure is essential for file uploads — the file's binary bytes can't be safely URL-encoded as a key=value pair.
The format is defined in RFC 7578. Browsers automatically use it when an HTML form has enctype="multipart/form-data" and contains an <input type="file"> element. APIs that accept file uploads typically require this Content-Type for the upload endpoint.
How multipart/form-data works
The Content-Type header includes a boundary — a randomly-generated string that separates each part in the request body:
POST /api/upload HTTP/1.1
Host: api.example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 554
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="username"
alice
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg
[binary JPEG bytes here]
------WebKitFormBoundary7MA4YWxkTrZu0gW--Each part is preceded by -- + the boundary string. Each part has its own Content-Disposition header (with the field name and optional filename) and optional Content-Type. Text fields don't need a Content-Type; file fields typically include one (image/jpeg, application/pdf, etc.). The body terminates with -- + boundary + --.
The boundary must be unique enough to never appear in the file contents, which is why browsers and HTTP clients generate long random strings.
multipart/form-data vs application/x-www-form-urlencoded
| Aspect | multipart/form-data | application/x-www-form-urlencoded |
|---|---|---|
| File uploads | Yes (designed for it) | No (binary data unsafe to URL-encode) |
| Binary fields | Yes | No |
| Text fields | Yes | Yes |
| Body format | Boundary-separated parts | key1=val1&key2=val2 |
| Encoding overhead | ~few hundred bytes (boundaries + headers) | URL-encoding adds ~3x for special chars |
| Body size | Large (file payloads) | Small (text only) |
| Browser default | When form has type=file | Default for text-only forms |
For text-only API requests, JSON (application/json) has largely replaced both form encodings. multipart/form-data remains essential for file uploads.
When to use multipart/form-data
- File uploads. The original use case. Profile pictures, document uploads, CSV imports, video uploads — any binary file going to a server.
- Mixed text + file forms. An "edit profile" form with name, bio, and avatar. multipart handles the avatar bytes alongside the text fields in one request.
- Multiple files in one request. Upload an album of photos with one POST. Each file is a separate part with the same name (
files[]) or different names. - Form submission with non-ASCII text. multipart handles UTF-8 better than URL-encoding (which bloats non-ASCII characters via percent-encoding).
Sending multipart/form-data from code
JavaScript (browser fetch + FormData)
const fd = new FormData();
fd.append('username', 'alice');
fd.append('avatar', fileInput.files[0]);
fetch('/api/upload', {
method: 'POST',
body: fd // Browser sets Content-Type with boundary automatically
});Important: do NOT manually set Content-Type when sending FormData — the browser needs to add the boundary itself.
curl
curl -X POST https://api.example.com/upload \
-F "username=alice" \
-F "avatar=@/path/to/photo.jpg"The -F flag tells curl to use multipart/form-data. @ prefix means "read from this file".
Python (requests)
import requests
files = {'avatar': open('photo.jpg', 'rb')}
data = {'username': 'alice'}
requests.post('https://api.example.com/upload', files=files, data=data)Node.js (form-data package)
const FormData = require('form-data');
const fs = require('fs');
const form = new FormData();
form.append('username', 'alice');
form.append('avatar', fs.createReadStream('photo.jpg'));
form.submit('https://api.example.com/upload', (err, res) => { ... });Server-side parsing
Servers need a multipart parser to read the request body. Most web frameworks include one:
- Express + Multer.
app.post('/upload', multer().single('avatar'), handler) - Django. Built-in:
request.FILES['avatar'] - Flask.
request.files['avatar'] - FastAPI.
def upload(file: UploadFile) - Spring Boot.
@RequestParam("file") MultipartFile file - Rails.
params[:avatar]
Most parsers buffer the whole upload to disk or memory. For very large files, look for streaming parsers that process the file as it arrives.
Multipart upload limits and gotchas
- Server body size limits. Different servers cap request bodies differently. NGINX default 1 MB; AWS API Gateway 10 MB; Cloudflare 100 MB Pro. Above the limit, server returns 413 Payload Too Large.
- Memory pressure on uploads. Naive parsers buffer the whole file in memory. Use streaming parsers + disk-based temp storage for large uploads.
- Boundary collisions. Theoretically possible but extremely rare given long random boundaries. If it happens, the server can't parse the request.
- Charset for non-ASCII fields. The Content-Disposition filename can include UTF-8 via
filename*=UTF-8''encoded%20name.jpg— older clients may not support this. - CSRF on uploads. File-upload endpoints need the same CSRF protection as other state-changing endpoints. Don't skip it just because it's a multipart request.
- File-type validation. Don't trust the Content-Type the client sends. Validate based on actual file content (magic bytes) and re-extension files server-side.
Performance considerations
- Avoid sending large files through your API server. Use signed S3/Cloud Storage upload URLs — the client uploads directly to object storage, your API just generates the URL. Cuts server bandwidth and CPU.
- Chunked / resumable uploads. For very large files (videos, archives), use protocols like tus.io or AWS S3 multipart upload. Resumable on network interruption.
- Compression. multipart/form-data isn't typically compressed (file bytes are often already compressed). But the boundary overhead is small.
- HTTP/2. Helps with multi-file uploads via stream multiplexing.
Common errors
- 413 Payload Too Large. Body exceeds server limit. Increase server config or split the upload.
- 400 Bad Request — missing boundary. Content-Type header didn't include the boundary parameter, or client manually set Content-Type without it.
- 500 — multipart parser exception. Boundary inside file content (very rare); malformed parts; truncated upload.
- Empty file received. Client used FormData but manually set Content-Type, overriding the boundary. Don't manually set Content-Type with FormData.
Testing multipart uploads at scale
- Vary file sizes. Tests with only tiny placeholder files don't reflect real bandwidth + server processing.
- Use real file content. Random bytes don't compress; real images/PDFs have realistic compression characteristics.
- Test boundary cases. Just-under and just-over the server's max upload limit. Verify graceful 413 responses.
- Multi-region. Upload bandwidth varies hugely by region. Test from where your real users are.
- Concurrent uploads. Verify your server can handle N concurrent file uploads without OOM.
FAQ: multipart/form-data
Why is the boundary in the Content-Type header?
The server needs to know the boundary to parse the body. It can't derive it from the body itself (since the boundary appears in the body). Putting it in the Content-Type header makes it available before parsing the body.
Can I use multipart/form-data without files?
Yes. It works for text-only fields too. But for text-only forms, application/x-www-form-urlencoded or JSON is more efficient (no boundary overhead).
What's the difference between multipart/form-data and multipart/mixed?
Both are multipart, but multipart/form-data is specifically for HTML form submissions (each part has Content-Disposition: form-data with a name). multipart/mixed is more generic (used in email attachments, MIME messages).
How do I send JSON + a file together?
Two common patterns: (1) one part is text/JSON, another part is the file (server parses both); (2) embed the file as base64 inside JSON (simpler but ~33% size overhead). multipart is more efficient for large files.
Why does my upload fail when I set Content-Type manually?
Because you didn't include the boundary, or the boundary you set doesn't match what's in the body. Let your HTTP client set the header automatically.
What's the max file size for multipart uploads?
Depends on server config. NGINX default 1 MB; raise client_max_body_size. AWS API Gateway hard cap 10 MB. For larger files, use direct-to-S3 signed URLs or chunked uploads (tus.io).
Test multipart upload endpoints with LoadFocus
If you're load testing file upload APIs, LoadFocus runs JMeter and k6 scripts that can send realistic multipart payloads from 25+ cloud regions with up to 12,500 VUs. Sign up for a free tier at loadfocus.com/signup — no credit card — and run your first multipart upload test in under 5 minutes.
Related LoadFocus Tools
Put this concept into practice with LoadFocus — the same platform that powers everything you just read about.