Cheatsheet

MIME Types Cheatsheet

A MIME type, also called a media type or Content-Type, is a type/subtype label that tells a client what the bytes in an HTTP body, upload or email part are. This sheet is for developers who set response headers, configure a static file server or validate uploads. The confusion it clears up: only some names you see in the wild are registered with IANA, and text/javascript is now the correct one for scripts.

Quick reference

Anatomy of a media type

Part Example Rule
Type `text` One of the registered top-level types
Subtype `html` Up to 127 characters by the grammar, 64 recommended
Suffix `+json` in `application/ld+json` Says the structure follows that syntax
Parameter `charset=utf-8` Written after a semicolon, name is case-insensitive
Facet `vnd.` in `application/vnd.api+json` Vendor tree, `prs.` is personal, `x.` is unregistered

Type and subtype are case-insensitive, so Application/JSON equals application/json. The registry lists these top-level types: application, audio, font, haptics, image, message, model, multipart, text and video, plus example, which is reserved for documentation.

Text and web documents

Extension MIME type Defined by
`.html` `text/html` W3C HTML
`.css` `text/css` W3C CSS
`.js`, `.mjs` `text/javascript` RFC 9239
`.json` `application/json` RFC 8259
`.jsonld` `application/ld+json` W3C JSON-LD
`.webmanifest` `application/manifest+json` W3C Web App Manifest
`.xml` `application/xml` or `text/xml` RFC 7303
`.csv` `text/csv` RFC 4180, RFC 7111
`.md` `text/markdown` RFC 7763
`.txt` `text/plain` RFC 2046
`.ics` `text/calendar` RFC 5545
`.yaml`, `.yml` `application/yaml` RFC 9512

Images

Extension MIME type Defined by
`.png` `image/png` W3C PNG
`.jpg`, `.jpeg` `image/jpeg` RFC 2045, RFC 2046
`.gif` `image/gif` RFC 2045, RFC 2046
`.webp` `image/webp` RFC 9649
`.avif` `image/avif` Alliance for Open Media
`.apng` `image/apng` W3C PNG group
`.svg` `image/svg+xml` W3C SVG
`.ico` `image/vnd.microsoft.icon` IANA registration
`.bmp` `image/bmp` RFC 7903
`.tif`, `.tiff` `image/tiff` RFC 3302
`.heic` `image/heic` ISO/IEC

Fonts, audio, video

Extension MIME type Defined by
`.woff` `font/woff` RFC 8081
`.woff2` `font/woff2` RFC 8081
`.ttf` `font/ttf` RFC 8081
`.otf` `font/otf` RFC 8081
`.mp3` `audio/mpeg` RFC 3003
`.ogg` `audio/ogg` RFC 5334, RFC 7845
`.flac` `audio/flac` RFC 9639
`.m4a` `audio/mp4` RFC 4337
`.mp4` `video/mp4` RFC 4337, RFC 6381
`.mpeg` `video/mpeg` RFC 2045, RFC 2046
`.mov` `video/quicktime` RFC 6381

Binary, archives and documents

Extension MIME type Defined by
`.pdf` `application/pdf` RFC 8118
`.zip` `application/zip` IANA registration
`.gz` `application/gzip` RFC 6713
`.rar` `application/vnd.rar` IANA registration
`.wasm` `application/wasm` W3C WebAssembly
`.epub` `application/epub+zip` W3C EPUB
`.xlsx` `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` IANA registration
`.docx` `application/vnd.openxmlformats-officedocument.wordprocessingml.document` IANA registration
unknown binary `application/octet-stream` RFC 2045, RFC 2046

Names that are common but not registered

Seen in the wild Use instead
`application/javascript`, `text/ecmascript` `text/javascript`
`image/x-icon` `image/vnd.microsoft.icon`
`application/x-gzip` `application/gzip`
`application/x-tar`, `video/webm`, `audio/wav` Not in the IANA registry when checked, but servers and browsers use them

RFC 6838 says new types should not use an x- prefix, and widely deployed x- names can be registered under their current spelling.

Request and form types

Content-Type When
`application/json` JSON API bodies
`application/x-www-form-urlencoded` Default HTML form encoding
`multipart/form-data; boundary=...` File uploads, RFC 7578
`application/problem+json` Error bodies, RFC 9457
`application/octet-stream` Raw bytes, forced downloads

Common patterns

Send the right header from a Node server

const http = require('http');
http.createServer((q, s) => {
  s.setHeader('Content-Type', 'application/json; charset=utf-8');
  s.setHeader('X-Content-Type-Options', 'nosniff');
  s.end('{"ok":true}');
}).listen(8765);

Checked with curl, the response carried both headers:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff

Always pair an explicit type with nosniff so browsers do not guess.

Map extensions to types with a safe fallback

const path = require('path');
const types = {'.html':'text/html; charset=utf-8','.js':'text/javascript; charset=utf-8','.json':'application/json','.svg':'image/svg+xml','.woff2':'font/woff2','.wasm':'application/wasm'};
for (const f of ['a.html','b.js','c.json','d.svg','e.woff2','f.wasm','g.xyz'])
  console.log(f, '->', types[path.extname(f)] ?? 'application/octet-stream');
a.html -> text/html; charset=utf-8
b.js -> text/javascript; charset=utf-8
c.json -> application/json
d.svg -> image/svg+xml
e.woff2 -> font/woff2
f.wasm -> application/wasm
g.xyz -> application/octet-stream

Falling back to application/octet-stream is what RFC 9110 suggests when the type is unknown.

Guess a type from a filename in Python

import mimetypes
for f in ['a.json','a.mjs','a.svg','a.tar.gz','a.unknown']:
    print(f, mimetypes.guess_type(f))
a.json ('application/json', None)
a.mjs ('text/javascript', None)
a.svg ('image/svg+xml', None)
a.tar.gz ('application/x-tar', 'gzip')
a.unknown (None, None)

The second tuple item is the content encoding, so .tar.gz comes back as tar plus gzip.

Fix a missing type in nginx

http {
  include /etc/nginx/mime.types;
  types { text/javascript js mjs; }
  default_type application/octet-stream;
}

On nginx 1.24 with the stock Ubuntu mime.types, .js came back as application/javascript and .mjs as application/octet-stream. With the types line above, both returned text/javascript. Nginx prints a "duplicate extension" warning when you override one, which is harmless.

See what fetch and Blob set for you

console.log(new Blob(['{}'], {type: 'Application/JSON; Charset=UTF-8'}).type);
console.log(new Response('hi').headers.get('content-type'));
console.log(new Response(new URLSearchParams({a: '1'})).headers.get('content-type'));
console.log(new Response(JSON.stringify({a: 1})).headers.get('content-type'));
application/json; charset=utf-8
text/plain;charset=UTF-8
application/x-www-form-urlencoded;charset=UTF-8
text/plain;charset=UTF-8

A JSON string body is sent as text/plain, so set Content-Type: application/json yourself. Blob lowercases the type.

Parse a Content-Type value

import email.message
m = email.message.Message()
m['Content-Type'] = 'Text/HTML; charset=UTF-8; q=1'
print(m.get_content_type(), m.get_content_maintype(), m.get_content_subtype(), m.get_param('charset'))
text/html text html UTF-8

Do not split on / and ; by hand when a parameter value can be quoted, such as boundary="abc123".

Pitfalls

  • Serving scripts as application/octet-stream: with nosniff set, the Fetch standard blocks a script-like request when the type is missing or not a JavaScript MIME type, and a stylesheet unless the type is text/css. Browsers then refuse module scripts with a MIME error. Map .mjs and .js to text/javascript.
  • Using application/javascript in new code: RFC 9239 (2022) marks it obsolete and makes text/javascript the only common name. Browsers still accept the old one, but linters and new servers should not emit it.
  • Posting JSON with fetch and no header: a string body goes out as text/plain;charset=UTF-8, so frameworks that switch on Content-Type ignore it. Add headers: {'Content-Type': 'application/json'}.
  • Setting Content-Type: multipart/form-data by hand: the boundary parameter is missing, and the server cannot split the parts. Let FormData set the header.
  • Serving user uploads as text/html or image/svg+xml: both can run script in your origin. Store the type from your own allowlist, serve uploads from a separate domain, and use Content-Disposition: attachment where possible.
  • Trusting the browser's file.type: it comes from the file extension, not the content. Check magic bytes on the server, for example with file --mime-type, which printed application/json for a JSON file and application/octet-stream for a bare PNG signature.
  • Relying on the charset default: RFC 6657 replaced the fixed US-ASCII default for text/*, so each subtype defines its own default or has none. For HTML and CSS, send charset=utf-8 or declare it in the document. JSON needs no charset parameter.
  • Using an x- name as if it were registered: application/x-tar, audio/wav and video/webm work everywhere but are not in the IANA registry as of this check. Do not invent new x- names.

Related ZipKit tools

Related cheatsheets