Content-Type tells the recipient how to interpret the bytes in a message. It is not a filename hint and it is not what the client hopes to receive—that is Accept. Mixing those two headers is behind a surprising number of 406 and 415 incidents.
Send the exact representation
| Body | Content-Type | Detail that matters |
|---|---|---|
| API JSON | application/json | Send JSON bytes, not a JavaScript object |
| HTML | text/html; charset=utf-8 | Declare text encoding deliberately |
| CSS | text/css | Browsers may refuse incorrectly typed styles |
| PNG | image/png | Do not claim JPEG from the extension |
| Form with files | browser-generated multipart | Boundary is required |
await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sku: 'red-mug' }),
});
Let FormData own its boundary
const form = new FormData();
form.append('receipt', file);
await fetch('/api/receipts', { method: 'POST', body: form });
Do not add Content-Type: multipart/form-data yourself. The browser creates a boundary such as ----WebKitFormBoundary…; omitting it makes the server unable to split parts.
JSON uses Unicode and is ordinarily exchanged as UTF-8. If a client receives unexpected characters, inspect the actual response bytes and header before “fixing” the JSON parser.
Do not trust uploads by their label
An upload can claim image/png while containing HTML or executable content. Where security matters, inspect the file signature, store untrusted uploads separately, and serve them with X-Content-Type-Options: nosniff. The header is a useful contract between cooperative systems, not proof that an attacker supplied truthful bytes.
