Mismatched content types are behind countless '400 Bad Request' and 'body is empty' bugs.
A body is just bytes; Content-Type tells the receiver how to interpret them, using a media type (MIME type) plus optional parameters.
application/json: the default for APIs. UTF-8 is required by the JSON spec, so no charset parameter is needed.text/html; charset=utf-8,text/plain,text/css,text/javascript.application/x-www-form-urlencoded: HTML form default:name=Ada&lang=en.multipart/form-data; boundary=...: forms with file uploads. Each field is a part separated by a boundary string.application/octet-stream: arbitrary binary data. Images, audio and video have their own types:image/png,image/avif,video/mp4.
Servers pick a parser based on the request's Content-Type. Send JSON without the header and many frameworks see an empty or text body; send the wrong one and you get 415 Unsupported Media Type.
The size comes from Content-Length, or, when the size isn't known in advance, from streaming: chunked transfer encoding in HTTP/1.1, or frames in HTTP/2 and 3.
Content-Disposition: attachment; filename="report.pdf" asks a browser to download rather than display a response.
Browsers may sniff content when the type is missing or vague; send X-Content-Type-Options: nosniff so a file uploaded as an image can't be executed as script. Always set an accurate type on responses, including charset=utf-8 for text.
Example
// JSON body: set the header AND serialise
await fetch("https://httpbin.org/post", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Ada", langs: ["en", "fr"] }),
});
// URL-encoded form body (fetch sets the Content-Type for URLSearchParams)
await fetch("https://httpbin.org/post", {
method: "POST",
body: new URLSearchParams({ name: "Ada Lovelace", lang: "en" }), // name=Ada+Lovelace&lang=en
});
// Multipart with a file (fetch sets multipart/form-data with a boundary)
const form = new FormData();
form.append("title", "Avatar");
form.append("file", new Blob(["fake png bytes"], { type: "image/png" }), "a.png");
const res = await fetch("https://httpbin.org/post", { method: "POST", body: form });
console.log(res.headers.get("content-type")); // application/jsonRemember
- Content-Type tells the receiver how to parse the body
- JSON APIs use application/json; forms use urlencoded or multipart
- File uploads need multipart/form-data
- Send nosniff and accurate types on responses
Watch out for
- Setting Content-Type: multipart/form-data by hand with fetch, which drops the boundary parameter; let fetch set it.