Every HTTP response your server sends includes a Content-Type header that tells the browser what kind of data it's receiving. Get it wrong and images don't render, JSON doesn't parse, downloads open as garbage text, and security headers become useless.
MIME types are the classification system behind Content-Type. This guide is a practical reference: what they are, when they matter, the complete type table for web development, and the security implications of getting them wrong.
What MIME Types Are
MIME (Multipurpose Internet Mail Extensions) types classify file content using a type/subtype format. The type is the broad category; the subtype is the specific format.
image/jpeg → type: image, subtype: jpeg
application/json → type: application, subtype: json
text/html → type: text, subtype: html
video/mp4 → type: video, subtype: mp4
font/woff2 → type: font, subtype: woff2The seven top-level types:
text/*— Human-readable text (html, css, javascript, plain, csv, xml)image/*— Images (jpeg, png, webp, avif, gif, svg+xml)audio/*— Audio (mpeg, ogg, wav, webm)video/*— Video (mp4, webm, ogg)application/*— Binary data and structured formats (json, pdf, zip, octet-stream)font/*— Fonts (woff2, woff, ttf, otf)multipart/*— Multi-part messages (form-data, byteranges)
MIME types can include parameters: text/html; charset=utf-8 specifies the character encoding. multipart/form-data; boundary=---abc specifies the boundary between form fields.
The Content-Type Header
The Content-Type header appears in both HTTP responses (server tells browser what format the response is) and HTTP requests (client tells server what format the request body is).
Response examples:
# HTML page
Content-Type: text/html; charset=utf-8
# JSON API response
Content-Type: application/json
# Image
Content-Type: image/webp
# File download
Content-Type: application/pdf
Content-Disposition: attachment; filename="report.pdf"
# CSS stylesheet
Content-Type: text/css; charset=utf-8
# JavaScript
Content-Type: text/javascript; charset=utf-8Request examples:
# JSON request body
POST /api/data HTTP/1.1
Content-Type: application/json
{"key": "value"}
# Form submission
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
# URL-encoded form
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=admin&password=secret
Complete MIME Type Reference for Web Development
| Extension | MIME Type | Category |
|---|---|---|
| .html, .htm | text/html | Document |
| .css | text/css | Stylesheet |
| .js, .mjs | text/javascript | Script |
| .json | application/json | Data |
| .xml | application/xml | Data |
| .csv | text/csv | Data |
| .txt | text/plain | Text |
| .jpg, .jpeg | image/jpeg | Image |
| .png | image/png | Image |
| .webp | image/webp | Image |
| .avif | image/avif | Image |
| .gif | image/gif | Image |
| .svg | image/svg+xml | Image |
| .ico | image/x-icon | Image |
| .bmp | image/bmp | Image |
| .tiff, .tif | image/tiff | Image |
| .mp4 | video/mp4 | Video |
| .webm | video/webm | Video |
| .ogg (video) | video/ogg | Video |
| .mp3 | audio/mpeg | Audio |
| .ogg (audio) | audio/ogg | Audio |
| .wav | audio/wav | Audio |
| .flac | audio/flac | Audio |
| .aac | audio/aac | Audio |
| .woff2 | font/woff2 | Font |
| .woff | font/woff | Font |
| .ttf | font/ttf | Font |
| .otf | font/otf | Font |
| application/pdf | Document | |
| .zip | application/zip | Archive |
| .gz | application/gzip | Archive |
| .wasm | application/wasm | WebAssembly |
| .map | application/json | Source map |
Content-Disposition: Inline vs Attachment
Content-Disposition controls whether the browser displays a file inline (in the page/tab) or triggers a download.
# Display PDF in browser's built-in viewer
Content-Type: application/pdf
Content-Disposition: inline
# Trigger download with suggested filename
Content-Type: application/pdf
Content-Disposition: attachment; filename="invoice-2026.pdf"
# Handle filenames with special characters
Content-Disposition: attachment; filename="file.pdf"; filename*=UTF-8''r%C3%A9sum%C3%A9.pdfWhen to use attachment: ZIP files, executables, documents the user explicitly requested to download. Without it, the browser might try to display the content inline (PDF viewers, text files).
When to use inline: Images, PDFs for viewing (not downloading), and any content you want the browser to render. inline is the default behavior when Content-Disposition is omitted.
Setting in various frameworks:
// Express.js
res.setHeader('Content-Disposition', 'attachment; filename="report.pdf"');
res.sendFile('/path/to/report.pdf');
// Or use the convenience method
res.download('/path/to/report.pdf', 'report.pdf');
// Cloudflare Workers
return new Response(pdfData, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="report.pdf"'
}
});
MIME Sniffing and X-Content-Type-Options
MIME sniffing is when the browser ignores the Content-Type header and guesses the file type by inspecting the content. This was a "helpful" feature in older browsers that's now a security vulnerability.
The attack: An attacker uploads a file named profile.jpg that actually contains JavaScript. Your server serves it with Content-Type: image/jpeg. Without MIME sniffing protection, the browser inspects the content, sees JavaScript, and executes it — XSS attack achieved.
The fix:
X-Content-Type-Options: nosniffThis header tells the browser: "Trust the Content-Type header. Don't guess." If the server says it's an image but the content looks like JavaScript, the browser refuses to execute it.
// Express.js — set on all responses
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
next();
});
// Or use helmet
import helmet from 'helmet';
app.use(helmet());
// Nginx
add_header X-Content-Type-Options nosniff always;
// Cloudflare Workers
const response = await fetch(request);
const newResponse = new Response(response.body, response);
newResponse.headers.set('X-Content-Type-Options', 'nosniff');
return newResponse;Set this header on every response. There is no downside — it only prevents the browser from overriding your explicit Content-Type, which is always the correct behavior.
multipart/form-data for File Uploads
When a form includes a file input, the browser encodes the submission as multipart/form-data. Each form field (including files) is separated by a boundary string.
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="title"
My Document
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="report.pdf"
Content-Type: application/pdf
[binary PDF data]
------WebKitFormBoundary7MA4YWxkTrZu0gW--Sending multipart from JavaScript:
const formData = new FormData();
formData.append('title', 'My Document');
formData.append('file', fileInput.files[0]);
fetch('/upload', {
method: 'POST',
body: formData,
// Do NOT set Content-Type manually — fetch sets it automatically
// with the correct boundary string
});Critical gotcha: Do not manually set Content-Type: multipart/form-data when using FormData with fetch(). The browser auto-generates the boundary string and sets the correct Content-Type. Setting it manually omits the boundary, and the server can't parse the request.
application/octet-stream: The Generic Binary Type
application/octet-stream means "this is binary data and I'm not telling you what kind." Browsers treat it as a download trigger — they won't try to render or execute it.
When you'll encounter it:
- Default Content-Type when a server doesn't know the file type
- Generic file download endpoints
- Binary API responses (Protocol Buffers, MessagePack, CBOR)
// Force download of any file type
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.sendFile(filePath);When to use it: Only when you genuinely don't know the file type or want to force a download regardless of format. If you know the type, always set the specific MIME type — it enables browser features like inline PDF viewing, image rendering, and audio/video playback.
Common MIME Type Mistakes
- Using
text/jsoninstead ofapplication/json:text/jsonis not a real MIME type. It works in most browsers but some strict parsers reject it. Always useapplication/json. - Using
application/javascriptinstead oftext/javascript: Both are valid, but the IANA standard since RFC 9239 (2022) istext/javascript. Stick with it for consistency. - Serving CSS without charset:
text/cssshould include; charset=utf-8if your CSS contains non-ASCII characters (common with icon fonts and content properties). - Serving SVG as
image/svg: The correct type isimage/svg+xml. Without the+xml, some browsers refuse to render it. - Serving WOFF2 as
application/font-woff2: The correct type isfont/woff2. Thefont/*top-level type was registered in 2017. Using the oldapplication/type still works but may cause warnings. - Missing Content-Type on API responses: If your API returns JSON without a Content-Type header, some HTTP clients refuse to parse the body as JSON. Always set
Content-Type: application/jsonon JSON responses.
MIME types are plumbing — invisible when correct, disastrous when wrong. The Content-Type header is one of the most important headers your server sends. Pair it with X-Content-Type-Options: nosniff on every response and you've closed a category of security vulnerabilities with one line of code.
Bookmark the reference table above. When converting between formats — JPG to WebP, CSV to JSON, HTML to PDF — the output MIME type changes too. Get both the file format and the Content-Type right.