MIME Types: What They Are and Why the Server Needs Content-Type
An image refuses to render, and the console prints a line about "resource interpreted as image but transferred with MIME type application/octet-stream". The site works, the file extension is correct, and it is unclear what the browser is complaining about. It is complaining about a response header, and that header carries the MIME type: one of the most underrated details of the web, the one nobody touches until something stops drawing.
What a MIME type is
A MIME type is a format label written as type/subtype. Text/html means an HTML document, image/png means a picture in PNG format, application/json means JSON data. The name is a relic of email: Multipurpose Internet Mail Extensions was the standard describing how to attach non-text content to messages, and the web borrowed the labeling scheme whole. Optional parameters follow after a semicolon, the most common being the character encoding: text/html; charset=utf-8.
The label lives in the Content-Type response header: the server sends not only the body but also a line promising what is inside. Based on that line the browser decides what to do with the content: draw it, apply it as a stylesheet, execute it as a script, show it as text, or offer it as a download. The same mechanism works in email and inside multipart messages, where each part carries its own type.
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 5412
<!DOCTYPE html>
<html lang="en">
...The main type groups
- text/: human-readable text, including html, css, javascript and plain text;
- image/: raster and vector pictures, png, jpeg, gif, svg, webp;
- application/: binary and application formats, json, pdf, zip, fonts;
- audio/ and video/: media files, mp3, mp4, webm;
- multipart/: multi-part messages, typically file uploads;
- font/: font formats like woff2, though they are often served as application/ types.
Why the browser does not guess
The file extension everyone relies on never reaches the browser in any trustworthy form: the URL can carry any extension, or none at all. The single official source of truth about the format is the Content-Type of the response. When the header is missing or uninformative, the browser switches to MIME sniffing: it reads the first bytes of the file and guesses from them. Guessing works on simple cases and fails on contested ones, so the protocol also defines a strict mode: the X-Content-Type-Options: nosniff header forbids guessing and forces the browser to trust the server exactly as stated.
A PNG without an extension demonstrates both behaviors. Served with an honest image/png header, the image opens regardless of its name. Served as application/octet-stream, the browser offers a download instead of showing it. With no header at all, sniffing kicks in and the outcome depends on the settings of the particular browser.
What breaks with the wrong MIME
- CSS served as text/plain: with nosniff on, the browser refuses to apply the stylesheet and the page loses its design;
- a script served as text/plain: execution is blocked under nosniff, with a MIME type error in the console;
- JSON served as text/html: clients and tools that trust the header refuse to parse the response, and devtools panels will not structure it;
- SVG served as application/octet-stream: the img tag will not render the picture and shows a placeholder;
- woff2 served as text/plain: under nosniff the font fails to load and text falls back to a system face;
- HTML served without a charset: the browser guesses the encoding and Cyrillic turns into mojibake, covered in the piece on UTF-8 and Unicode.
File: styles.css
Wrong: Content-Type: text/plain
Result: styles are not applied under nosniff
Correct: Content-Type: text/css; charset=utf-8
Result: styles apply, no open questionsThe minimum set for a website
- .html → text/html, always with charset=utf-8;
- .css → text/css;
- .js → text/javascript, the current standard;
- .json → application/json;
- .png → image/png;
- .jpg and .jpeg → image/jpeg;
- .svg → image/svg+xml;
- .webp → image/webp;
- .pdf → application/pdf;
- .txt → text/plain;
- .woff2 → font/woff2;
- .mp4 → video/mp4.
IANA maintains the full registry, which counts thousands of entries, but the list above covers nine sites out of ten. For rare extensions the MIME lookup reference answers by extension or by type name. Related reading on response codes lives in the piece about HTTP status codes, and JSON debugging habits in the article on formatting and validating JSON.
X-prefixes: where they came from and where they went
In names like application/x-javascript or image/x-icon, the letters x- stand for experimental, an unofficial subtype that never passed IANA registration. It used to be the standard move for any format the official registry had not reached, and half the web ran on x-names. In 2012, RFC 6648 dropped the prefix for new registrations: types are registered directly under a clean name, while the old x-entries persist for compatibility with historical systems.
The liveliest monument to that era is application/x-www-form-urlencoded: the default encoding of HTML forms still wears the x-prefix decades after becoming a de facto standard. When working with XML, the XML formatter helps make the content readable, and the distinction between application/xml and text/xml is one of historical labels rather than content, a theme shared with the comparison of XML and JSON.
Multipart and file uploads
A form with files cannot travel as urlencoded data: binary content gets corrupted by text encodings. The solution is multipart/form-data: the request body is split into parts, a random boundary string from the Content-Type header marks the seams, and every part carries its own headers with the field name and its own content type. The browser composes the boundary automatically, so there is nothing to assemble by hand, but seeing the structure once is worth it.
Content-Type: multipart/form-data; boundary=----X9aQ
------X9aQ
Content-Disposition: form-data; name="title"
November report
------X9aQ
Content-Disposition: form-data; name="file"; filename="doc.pdf"
Content-Type: application/pdf
%PDF-1.7 ...
------X9aQ--A second multipart flavor, multipart/byteranges, appears in range request responses: videos and large files download in chunks, and each chunk is labeled with its byte range. The structure is identical: one shared boundary, parts with their own headers.
Checking the MIME of a file and a response
- open the Network tab in DevTools, reload the page and pick the file in question;
- in Response Headers find the content-type line, that is what the server actually sent;
- to check without a browser, run curl -I against the file URL, headers arrive first;
- for a local file on Linux and macOS the file command detects the format from its signature, not the extension.
curl -I https://example.com/logo.svg
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Conclusion: wrong type, the image will not render in img.
Fix on your own host, .htaccess:
AddType image/svg+xml .svgOn a server you control, the fix lives in configuration: in Apache it is the AddType directive, and the full walkthrough is in the guide to .htaccess. On shared hosting, a support ticket quoting the curl output resolves quickly, because the problem reproduces with a single command.
Typical hosting mistakes
- .svg served as application/octet-stream, a classic of old configurations, images invisible;
- .woff2 not described at all, fonts load only when nosniff is off;
- .json served as text/html, client applications crash at parsing;
- .webp unknown to older servers, pictures silently fall back to png even when webp versions exist;
- no charset for text types, and pages with non-Latin text display in the wrong encoding.
Common mistakes
- mixing up the request and response Content-Type: the client sends application/json, the server answers text/html, and without inspecting headers the culprit is unclear;
- setting text/html for API responses so the browser shows something: browsers display application/json fine, while the tools break;
- deriving the type from the extension alone: a renamed file keeps the wrong type, the content is the honest source;
- forgetting charset on text types and getting mojibake on the page;
- duplicating the boundary when assembling multipart requests by hand, and the server truncates the body at the first part;
- enabling nosniff without auditing resource types: protection switches on and half the site stops applying.
Common questions
How is application/json different from text/javascript?
Identical in content, opposite in promise. text/javascript tells the browser the file can be executed as a script; application/json tells tools this is data to parse, not code to run. Serving JSON as application/octet-stream with client-side parsing is legal, but the application/json header removes every question before it arises.
Is charset=utf-8 mandatory in the header?
Formally it applies to text types, and not always even there. Practically, always specify it for text/html and text/css: without the parameter the browser guesses the encoding, and on pages with Cyrillic or other non-Latin scripts the guess regularly loses. JSON is UTF-8 by the standard itself, so the parameter is redundant there.
What does the browser do with an unknown type?
By default it offers to save the file: application/octet-stream and unregistered types are treated as download material rather than displayable content. That is why a misconfigured type is so conspicuous: the content does not break, it disappears from the page into the downloads folder.
Who approves MIME types?
IANA maintains the registry through a media type registration procedure. New formats file an application, and once registered the name becomes official. The practical takeaway is not to invent types: for unclassified content there is the neutral application/octet-stream, and for most formats an official name already exists.
Why does .js come with three different types?
History: application/x-javascript predates registration, application/javascript was the first official entry, and RFC 9239 in 2022 established text/javascript as the preferred form. Browsers accept all three, so old configurations keep working, but new documentation should quote the current standard.
MIME Type Lookup
Look up a MIME type by file extension or by type itself: 83 entries, category filter and one-click copy
HTTP Status Code Reference
Complete HTTP status code reference with search, color-coded categories, and click-to-copy
XML Formatter & Beautifier
Format, beautify, minify, and validate XML documents