multipart/form-data
Multipart form datarequest body
multipart/form-dataThe request body for forms that upload files. Each field travels as its own part, and a boundary string separates the parts.
| MIME type | multipart/form-data |
|---|---|
| Used for | Request body |
| Parameters | boundary — required, generated by the client |
| Specification | RFC 7578 |
What it looks like on the wire
POST /upload HTTP/1.1 Host: api.example.com Content-Type: multipart/form-data; boundary=----devkult7MA4YWxk ------devkult7MA4YWxk Content-Disposition: form-data; name="title" Quarterly report ------devkult7MA4YWxk Content-Disposition: form-data; name="file"; filename="report.pdf" Content-Type: application/pdf %PDF-1.7 …binary data… ------devkult7MA4YWxk--
Content-Type: multipart/form-data
Send it
<form action="/upload" method="post" enctype="multipart/form-data"> <input name="title"> <input type="file" name="file"> <button>Upload</button> </form>
const body = new FormData();
body.append("title", "Quarterly report");
body.append("file", fileInput.files[0]);
// Don't set Content-Type yourself: the browser adds it with the boundary.
await fetch("/upload", { method: "POST", body });curl -F title="Quarterly report" -F file=@report.pdf https://api.example.com/upload
Receive it
import multer from "multer";
const upload = multer({ dest: "uploads/", limits: { fileSize: 10 * 1024 * 1024 } });
app.post("/upload", upload.single("file"), (req, res) => {
// req.body.title, req.file.originalname, req.file.size
});export async function POST(req: Request) {
const form = await req.formData();
const file = form.get("file") as File;
const bytes = await file.arrayBuffer();
}from werkzeug.utils import secure_filename
@app.post("/upload")
def upload():
title = request.form["title"]
f = request.files["file"]
f.save(os.path.join(UPLOAD_DIR, secure_filename(f.filename)))r.ParseMultipartForm(10 << 20) // up to 10 MB in memory, the rest on disk
file, header, err := r.FormFile("file")Good to know
- Never set
Content-Type: multipart/form-databy hand when sending aFormDatabody with fetch or axios. Without theboundaryparameter the server can't split the parts — the usual cause of an emptyreq.bodyor a "Multipart: Boundary not found" error. - Express's
express.json()andexpress.urlencoded()skip multipart bodies. You need a multipart parser such as multer, busboy or formidable. - A part's
filenameand its ownContent-Typeare whatever the client claims. Check the file's signature on the server and generate your own filename before saving — see the MIME type list for signatures. - For a single large file, a raw
PUTwith the file's own type (or a presigned upload URL) avoids the multipart overhead.
Frequently asked questions
When should I use multipart/form-data instead of application/x-www-form-urlencoded?
Whenever the form includes a file. URL encoding has no efficient way to carry binary data, while multipart sends each file as raw bytes in its own part. For forms with only short text fields, x-www-form-urlencoded is smaller and simpler.
What is the boundary in multipart/form-data?
A random string that doesn't occur in the content, declared in the header as boundary=…. Each part starts with -- plus the boundary, and the body ends with --, the boundary and -- again. When you use FormData or curl -F, the client picks it for you.
Why is req.body empty when I send FormData?
Either the server has no multipart parser (Express needs multer or similar), or the Content-Type was set by hand and lacks the boundary. Remove the manual header and let the browser set it.
Headers that go with it: Content-Type · Content-Disposition · Content-Length