HTTP Content Types
Meanings and examples of common HTTP Content-Type media types — JSON, HTML, forms, images, audio, video and file downloads — with charset and multipart boundary notes.
How It Works
The Content-Type response header tells the client what format the byte stream is — text/html, application/json, image/png — which decides whether the browser renders a page, parses JSON or downloads a file.
Its value is a MIME type plus optional parameters, most often charset (e.g. application/json; charset=utf-8) declaring the text encoding. It is unrelated to the file extension: a server can serve .html as text/plain and the client obeys only Content-Type.
A good practical rule: declare the specific subtype whenever you can. application/json is better than a vague text/plain because it lets servers parse, lets CDNs decide on compression, and lets browsers decide whether to render or download. Fall back to text/plain or application/octet-stream only when the format is genuinely unknown, and in that case add Content-Disposition so downstream behaviour is predictable. Also note that GET request bodies have no guaranteed length and many servers and proxies drop them outright — use POST plus query parameters instead of relying on them.
When to Use This Table
This table matters when you need to decide what format a body is in. Content-Type is the most routinely ignored yet most behaviour-shaping HTTP header: it drives server-side parsing, browser rendering, and whether a CDN may cache the response.
When not to consult it: you are debugging file upload or static-asset types. The extension-to-MIME mapping lives in a different table (the in-site MIME Types page), and the browser fallback policy for unknown types is a separate topic again.
Rule of thumb: if you are asking "why did the server not parse my body", it is almost always a missing or wrong Content-Type, so consult this table. If you are asking "why did JSON turn into a download", look at Content-Disposition and nosniff instead.
Another bad signal: you are about to add a Content-Type but cannot state what format the body is. A wrong type is worse than none — some servers reject outright rather than ignoring it.
Boundaries and Common Pitfalls
When Content-Type is missing, servers guess differently
Express body-parser parses nothing by default (req.body is never initialised without a declared type); some frameworks fall back to application/octet-stream; PHP splits behaviour between $_FILES and form rules. The same JSON payload therefore fails in completely different ways across stacks: req.body undefined, a thrown parse error, or treated as plain text. When "I sent a body but it is not read", check Content-Type before suspecting the framework.
charset is not an optional decoration
HTTP specifies UTF-8 as the default charset for application/json, but the default for text/* is implementation-defined and often ISO-8859-1. The safe habit is to always state it explicitly, e.g. application/json; charset=utf-8 — mandatory for non-ASCII payloads. With Chinese text, a missing charset often surfaces as a decoding failure on the server rather than as mojibake in the browser.
Forms with file uploads cannot be application/json
multipart/form-data (with its boundary parameter) is the browser-native way to submit files, and it is mutually exclusive with application/x-www-form-urlencoded. Putting files into JSON as base64 inflates size by about 33 percent and loses streaming, so large payloads will exceed limits. When you need structured data, send fields and files together in multipart — do not encode the file into JSON.
Content-Type does not decide CORS, but it does interact with preflight
CORS failures are often misdiagnosed as Content-Type problems. The real chain: custom headers (e.g. Authorization) or non-simple Content-Type trigger an OPTIONS preflight, and it is the preflight that fails. application/json is "simple" enough, but a custom Authorization header always forces a preflight. When debugging, look for the OPTIONS request and its response headers first instead of tweaking Content-Type.
How This Relates to Neighbours
For the full extension-to-MIME mapping (.png, .woff2, .wasm and more) and which types render inline, use the in-site MIME Types page; this page only covers Content-Type semantics in HTTP communication.
When a response triggers a download instead of rendering, Content-Type is only part of it — also check Content-Disposition and X-Content-Type-Options: nosniff, which this table does not cover.
Turning bytes back into text is the Character Encodings page; this page only covers how the encoding is declared (the charset parameter).
To pretty-print a JSON payload for reading, use the in-site JSON formatter; this table offers no JSON validation.
Practical Snippet
Key points: (1) validate Content-Type server-side and return 415 rather than letting the parser fail silently, so a missing type surfaces during development; (2) write charset=utf-8 explicitly — the default for text/* is implementation-defined; (3) with non-ASCII payloads (Chinese, emoji), a missing charset usually breaks decoding on the server, and the browser only shows the aftermath.
// Express:显式声明 charset,并统一错误响应
app.post('/api/echo', express.json({ limit: '1mb' }), (req, res) => {
// req.headers['content-type'] 形如 "application/json; charset=utf-8"
const ct = req.headers['content-type'] || ''
if (!ct.startsWith('application/json')) {
return res.status(415).json({
error: 'Unsupported Media Type',
got: ct || '(missing)',
})
}
res.json({ ok: true, keys: Object.keys(req.body ?? {}) })
})
// 前端:JSON 请求显式带 charset
await fetch('/api/echo', {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify({ name: '张三', emoji: '🛠' }),
})Practical Notes
- charset comes from Content-Type, not the HTML metaA response header Content-Type: text/html; charset=utf-8 takes precedence over <meta charset>. If the header says GBK, an in-page UTF-8 declaration changes nothing — a frequent cause of "fine locally, mojibake in production".
- A multipart/form-data boundary must match the body exactlyUpload forms carry an auto-generated boundary parameter. When building the request by hand, the body delimiters must match byte for byte, including the closing --boundary-- and its CRLF; one missing newline yields a vague "cannot parse file" error.
- Content-Type and Accept are different headersAccept states what the client can receive; Content-Type states what the sender is sending. Deciding a response body from Accept alone, or describing an expectation via Content-Type, both break content negotiation.
Data Sources
- RFC 9110 §8.3 (representation metadata)
- IANA Media Types Registry
- RFC 2046 (multipart)
Codes, names and definitions above come from public specifications; we claim no originality over the specification data itself. The arrangement, Chinese annotations and practical notes on this page are written by us.
| Content-Type | Meaning | Example |
|---|---|---|
| application/json | JSON structured data | Content-Type: application/json; charset=utf-8 |
| application/xml | XML data | application/xml; charset=utf-8 |
| text/html | HTML document | text/html; charset=utf-8 |
| text/plain | Plain text | text/plain; charset=utf-8 |
| application/x-www-form-urlencoded | Default HTML form encoding | application/x-www-form-urlencoded |
| multipart/form-data | File upload form | multipart/form-data; boundary=----WebKitFormBoundaryABC |
| application/octet-stream | Unknown binary stream (triggers download) | application/octet-stream |
| image/png | PNG image | image/png |
| image/jpeg | JPEG image | image/jpeg |
| image/svg+xml | SVG vector image | image/svg+xml |
| audio/mpeg | MP3 audio | audio/mpeg |
| video/mp4 | MP4 video | video/mp4 |
| application/pdf | PDF document | application/pdf |
| application/zip | ZIP archive | application/zip |
| font/woff2 | WOFF2 font | font/woff2 |
| application/javascript | JavaScript script | application/javascript; charset=utf-8 |
| application/ld+json | Structured data (JSON-LD) | application/ld+json |
| application/gzip | Gzip compressed data | application/gzip |
Frequently Asked Questions
What happens if charset is omitted in Content-Type?
Without charset, the browser guesses the encoding (often ISO-8859-1 or a locale default), and UTF-8 text like Chinese easily turns into mojibake. Always set charset=utf-8 for text types. Binary types (image/*, application/octet-stream) do not need charset.
What is the boundary in multipart/form-data?
boundary is the delimiter separating each form field, prefixed with two hyphens (e.g. --boundary), used to split parts. The client generates it randomly; you never write it by hand — the server parses fields and files by the Boundary.
Should I use application/json or text/json?
Use application/json — it is the IANA-registered, universally recognized type. text/json is non-standard and may be misclassified by legacy systems or firewalls, so avoid it.
Why do download endpoints return application/octet-stream?
octet-stream means "unknown binary"; browsers usually will not preview it and instead trigger a download (especially with Content-Disposition: attachment). Good for exports, installers and similar files.