What Are MIME Types? Content-Type Headers Explained
Photo by Unsplash on Unsplash
Table of Contents
The Label on Every Piece of Data
The first time MIME types really clicked for me was when an API endpoint returned perfectly valid JSON, but my front-end kept treating the response as a string. The data was fine. The server was sending `Content-Type: text/plain` instead of `application/json`, so the browser never parsed it. One header value, an hour of confusion.
MIME types are quietly load-bearing across the whole web. Here's what they are, how the Content-Type header uses them, and the specific ways they break things when they're wrong.
The type/subtype Format
The common top-level types are:
- `text` — human-readable text. `text/html`, `text/plain`, `text/css`, `text/csv`. - `application` — structured or binary data for programs. `application/json`, `application/pdf`, `application/zip`, `application/octet-stream` (the generic "raw bytes" type). - `image` — `image/png`, `image/jpeg`, `image/svg+xml`, `image/webp`. - `audio` and `video` — `audio/mpeg`, `video/mp4`, and so on. - `multipart` — data made of multiple parts, like `multipart/form-data` for file uploads.
After the type and subtype, you can tack on parameters separated by semicolons. The most common is the character encoding:
``` Content-Type: text/html; charset=utf-8 ```
That `charset=utf-8` tells the browser to decode the bytes as UTF-8, which matters the moment your page has any character outside basic ASCII. Leave it off and you risk mojibake — those garbled `é` sequences where an é should be.
You'll also run into structured syntax suffixes — that `+xml` or `+json` tacked onto a subtype, like `image/svg+xml` or `application/ld+json`. The suffix tells you the underlying format the data is built on: `image/svg+xml` is an SVG image that's *also* valid XML, and `application/ld+json` is JSON-LD, which is JSON underneath. Tools that don't recognize the specific type can fall back on the suffix to at least parse the base format. It's a small detail, but it's why you sometimes see these longer-looking types in the wild and they're not typos.
Then there are vendor and experimental types. A subtype starting with `vnd.` is a vendor-specific format — `application/vnd.ms-excel` for old Excel files, for instance — while `x-` historically marked unregistered, experimental types like `application/x-www-form-urlencoded` (the format a plain HTML form POSTs by default). You don't need to memorize these, but recognizing the `vnd.` and `x-` prefixes helps you read a Content-Type header without panicking when it looks unfamiliar.
A naming detail that confuses people: these are interchangeably called MIME types, media types, and content types. They're the same thing. "MIME type" is the oldest term, going back to Multipurpose Internet Mail Extensions, where the `type/subtype` format was invented to label email attachments. "Media type" is the term the standards bodies prefer now. "Content type" comes from the HTTP header name. Same concept, three names, and you'll see all three in docs and Stack Overflow answers.
How the Content-Type Header Works
Photo by Unsplash on Unsplash
On a response, the server uses it to tell the browser what it's sending. When you load a page, the server replies with `Content-Type: text/html; charset=utf-8`, and the browser knows to parse and render it as HTML. Request a JSON API and a well-behaved server sends `Content-Type: application/json`, which is the signal that lets `response.json()` work in your fetch call. If you've ever wired up a fetch and been surprised the JSON didn't parse, this header is the first thing I'd check — I walked through the whole flow in how to read JSON from an API in JavaScript.
On a request, *you* set it to tell the server what you're sending. When you POST data, the server needs to know how to interpret the body:
``` fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Alice' }) }) ```
Forget that header on a POST and a lot of back-end frameworks won't parse the body — they'll see raw bytes they don't know how to handle, and your `req.body` comes back empty. This is one of the most common "my API receives nothing" bugs, and it's almost always a missing or wrong Content-Type on the request.
The official MIME type for JSON is `application/json`, registered with IANA back in 2006, and it's what every JSON API should send and expect. The MDN guide to common MIME types has the full reference table if you need to look up a specific format. The deeper point is that the Content-Type header is a contract: the sender declares the format, and the receiver trusts that declaration to decide how to process the bytes.
When MIME Types Go Wrong
JSON served as text/plain. The API returns valid JSON but labels it `text/plain`, so the browser hands you a string instead of a parsed object. Your code calls `response.json()` and it works (because fetch parses based on your call, not the header) — but tools, proxies, and some libraries that *do* trust the header will treat it as text. Always send `application/json` for JSON.
An HTML file that downloads instead of rendering. If a server sends an `.html` file with `Content-Type: application/octet-stream` (the generic binary type), the browser doesn't know it's a page — it just sees raw bytes and offers to download the file. Fixing the content type makes it render normally.
A script blocked for the wrong type. Modern browsers enforce strict MIME checking on scripts. If your `.js` file is served as `text/plain` instead of `application/javascript` (or `text/javascript`), the browser refuses to execute it and logs a console error about a disallowed MIME type. This catches people deploying static files from a misconfigured server.
File-upload validation by extension alone. A `.jpg` file is just bytes; the extension is a hint, not a guarantee. Someone can rename `evil.exe` to `photo.jpg`. Real upload validation checks the actual MIME type (and ideally the file's magic-number signature), not just the filename. Trusting the extension is a genuine security hole.
The pattern across all of these: the MIME type is a claim, and software acts on the claim. When the claim is wrong, behavior goes sideways in ways that look mysterious until you open the network tab and read the actual Content-Type header.
There's a flip side to Content-Type worth knowing: the `Accept` header, which the *client* sends to say what formats it would prefer back. A browser loading a page sends something like `Accept: text/html`, while a JavaScript fetch expecting JSON might send `Accept: application/json`. A well-built API reads that header and serves the matching format — this is called content negotiation, and it's how a single endpoint can return JSON to your app and HTML to a browser pointed at the same URL. You don't have to use it, but it explains why hitting an API URL directly in your browser sometimes gives you a formatted page and sometimes a raw blob: the server is reacting to what your client said it accepts. The Content-Type on the response and the Accept on the request are two halves of the same conversation — one declares, the other requests.
Data URIs are another place MIME types show up in plain sight. A data URI bakes the MIME type right into a string: `data:image/png;base64,iVBOR...` starts with `data:`, then the MIME type, then the encoding, then the actual bytes. That's the same `image/png` you'd see in a Content-Type header, just embedded inline instead of sent over HTTP. If the MIME type in a data URI is wrong, the browser refuses to render it for exactly the same reason a misconfigured server breaks an image — the claim doesn't match the bytes.
When I'm debugging one of these, I'll grab the raw response, paste it into the ToolsFuel JSON formatter to confirm the body is what I think it is, then check whether the header agrees. The Base64 encoder/decoder comes up too, since data URIs bake the MIME type right into the string — something I covered in data URIs vs external images. The fix is nearly always the same: make the header tell the truth about the bytes.
Frequently Asked Questions
What is a MIME type?
A MIME type is a short string that identifies the format of a piece of data, written as type/subtype — like text/html, application/json, or image/png. Software uses it to decide how to handle the data: render it, parse it, display it, or download it. It's sent in the HTTP Content-Type header, and the same concept is also called a media type or content type.
What's the correct MIME type for JSON?
The official MIME type for JSON is application/json, registered with IANA in 2006. Every JSON API should send it in the Content-Type response header and expect it on requests. Sending JSON as text/plain instead is a common mistake that causes tools and libraries trusting the header to treat the data as a plain string instead of structured JSON.
What does charset=utf-8 mean in a Content-Type header?
It's a parameter that tells the browser which character encoding to use when decoding the bytes — UTF-8 in this case. It matters whenever your content includes characters outside basic ASCII, like accented letters or emoji. Leaving it off can produce mojibake, those garbled character sequences where an é shows up as é. The format is Content-Type: text/html; charset=utf-8.
Why does my API receive an empty request body?
The most common cause is a missing or wrong Content-Type header on the request. When you POST JSON, you must send Content-Type: application/json so the back end knows how to parse the body — otherwise many frameworks leave req.body empty. I walk through reading and sending API data correctly in [how to read JSON from an API in JavaScript](/blog/how-to-read-json-from-api-javascript-fetch).
Is a file's extension the same as its MIME type?
No. The extension is a filename hint, while the MIME type is the actual declared format in the Content-Type header. They usually agree, but someone can rename a file to fake its extension, which is why secure upload validation checks the real MIME type and file signature rather than trusting the name. The two are related but independent.
Why does my JavaScript file fail to load with a MIME type error?
Modern browsers enforce strict MIME checking on scripts and refuse to execute a .js file served as text/plain instead of application/javascript or text/javascript. This usually happens with a misconfigured static server. The fix is to configure the server to send the correct content type for JavaScript files. You can explore related encoding tools in the [ToolsFuel tools directory](/tools).
Try ToolsFuel
23+ free online tools for developers, designers, and everyone. No signup required.
Browse All Tools