How to parse a Content-Type header
By Flavio
Parse a Content-Type header into a media type, parameters, charset, and boundary. See what a quoted boundary, a duplicate charset, and a trailing semicolon do.
Free toolThis tutorial uses the Content-Type Parser. It runs in your browser, so open it in another tab and follow along.
Content-Type tells you what a body is. An HTML page is text/html. A JSON API is application/json. A form upload is multipart/form-data plus a boundary string that splits the parts.
The header is not only the type. After the type come parameters, separated by semicolons. charset=utf-8 is a parameter. boundary=something is a parameter. If you split on the first semicolon and throw the rest away, you lose the charset, and you lose the boundary, and a multipart body becomes impossible to read.
The Content-Type parser reads one header value. It uses the grammar in RFC 9110, which is the current HTTP semantics spec. You paste the value, without the Content-Type: label. You get the type, the subtype, every parameter, and a canonical line you can compare or log.
An HTML type with a charset
Paste text/html; charset=utf-8.
Media type: text/html
Type: text
Subtype: html
Parameters: 1
charset = utf-8
Charset: utf-8
text/html; charset=utf-8
The media type is text/html. The type and the subtype are the two halves. There is one parameter, charset, and its value is utf-8. The blank line, then the canonical form. Here the canonical form matches the input, because the input was already lowercase and the value was already a token.
A token is a run of letters, digits, and a short list of marks such as hyphen, dot, plus, and underscore. A token value is printed without quotes. The page lowercases the type, the subtype, and the parameter name. It does not lowercase the value. A boundary is case-sensitive, so those characters stay as you pasted them.
A multipart boundary in quotes
Paste this. The quotes are part of the header:
multipart/form-data; boundary="----WebKitFormBoundary7MA4YWxkTrZu0gW"
Media type: multipart/form-data
Type: multipart
Subtype: form-data
Parameters: 1
boundary = ----WebKitFormBoundary7MA4YWxkTrZu0gW
Boundary: ----WebKitFormBoundary7MA4YWxkTrZu0gW
multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
The quotes are gone in the parameter value. They were syntax, not data. The boundary you look for in the body is the string between the quotes, dashes included.
The quotes around boundary were removed. The value is a token, so quotes are optional.
That note is the reason the canonical line has no quotes. The value is a token, so the quotes were optional in the first place. A value that is not a token, because it contains a space or a character outside that set, keeps its quotes in the canonical line.
The boundary is what you split the body on. The body contains -- plus the boundary between parts, and -- plus the boundary plus -- after the last part. If you keep the quote characters in the string you search for, you will not find the parts.
A duplicate charset
Paste Text/HTML; charset=utf-8; charset=iso-8859-1.
Media type: text/html
Type: text
Subtype: html
Parameters: 2
charset = utf-8
charset = iso-8859-1
Charset: utf-8
text/html; charset=utf-8; charset=iso-8859-1
The type was uppercased and is now text/html. Both parameters are kept, in order. The charset field shows the first one.
The charset parameter appears more than once. This page uses the first one.
HTTP says the parameter names are case-insensitive, and it does not pick a winner when the same name appears twice. Browsers and servers disagree in the wild. This page keeps both in the canonical line, so you can see the duplicate, and it reports the first as the charset. If you are debugging a response that looks like the wrong encoding, look at this note before you blame the file.
A trailing semicolon, and a quoted quote
Paste text/plain;.
Media type: text/plain
Type: text
Subtype: plain
Parameters: 0
text/plain
An empty parameter was ignored.
The semicolon with nothing after it is an empty parameter. It is not a syntax error. The canonical line drops it. A space before the semicolon is fine too. Paste text/plain and the media type is still text/plain.
Leading or trailing whitespace was removed.
Paste text/plain; name="a\"b".
Media type: text/plain
Type: text
Subtype: plain
Parameters: 1
name = a"b
text/plain; name="a\"b"
Inside quotes, a backslash escapes the next octet. \" is a quote character in the value, so the value is a"b: the letter a, a quote, and the letter b. The canonical line puts the quotes back and writes the backslash again, because a quote inside a quoted value has to be escaped. The summary shows the raw value on the name = line, and the escaped form on the canonical line. Those two lines are supposed to look different.
What the page refuses
An empty box says: Paste a Content-Type header value.
A value with no slash says: A media type needs a type, a slash, and a subtype.
text alone fails that way. text/ fails it too. The subtype has to be a token.
A space around the equals sign says: HTTP does not allow whitespace around ’=’ in a parameter.
text/plain; charset = utf-8 looks readable and is illegal in HTTP. The optional whitespace is allowed around the semicolon. It is not allowed around =. Joining the pieces with spaces around the equals sign produces a header the grammar rejects.
A parameter with no equals sign says: A parameter needs an ’=’.
A quoted value that never closes says: A quoted parameter value never closed.
A backslash at the end of a quoted value says: A backslash in a quoted value is not followed by a character.
A character HTTP does not allow inside quotes, such as a raw DEL or a raw line feed, says: A quoted value contains a character HTTP does not allow there.
A character above U+00FF anywhere in the value says: A Content-Type value is bytes. This one has a character above U+00FF.
HTTP header values are octets. A emoji in a boundary is not a header value this grammar can carry. The page stops at 8,000 characters with: The header is over 8,000 characters. This page stops there.
Parentheses are not comments here. MIME has comments. HTTP media types do not. A ( in the type or in a token is unexpected text, and the page says so.
Multipart with no boundary
Paste multipart/form-data and nothing else.
Media type: multipart/form-data
Type: multipart
Subtype: form-data
Parameters: 0
multipart/form-data
This is a multipart type and it has no boundary parameter.
The header parses. It is still not useful. Without a boundary you cannot find the parts. The page reports the type and adds that note, instead of inventing a boundary. The same note fires for multipart/mixed and for any other multipart/ subtype.
The page does not invent a charset either. text/plain with no charset parameter stays without one. The spec has a default for some text types in some contexts. Guessing it here would hide the fact that the header omitted it.
When a different tool is the right one
This page reads a header you already have. It does not tell you which type a file extension maps to. That table is the MIME type lookup. The other direction, from a type back to an extension in Node, is the file extension from a MIME type post. mime.extension('text/plain') wants the media type, not the whole header. Run the header through this page first if a charset is stuck on the end, then pass text/plain to the package.
A body of type application/x-www-form-urlencoded is a list of fields, not a media type problem. The form converter parses that body. The header only tells you that you should.
The HTTP response headers list is the map of the other header names. Content-Type is one line in it. This page is the close reading of that one line.
The headers explainer is useful when you have a whole response and you want each header named. Come here when the question is specifically what the type, the charset, and the boundary are.
The header stays in the browser. It is not written into the URL. Paste a value from a failing request, read the canonical line, and you can see whether the bug is the type, a duplicate charset, or a boundary that still has its quotes on.
Try the Content-Type Parser
Parse a Content-Type header into a media type, charset, and boundary. Quoted parameters are unescaped. The header stays in the browser.
Open the Content-Type Parser →
Related tools
- MIME Type Lookup: Extension to MIME type and back — 150+ entries with Content-Type examples, charset notes, and gotchas.
- URL-encoded Form to JSON: Convert an application/x-www-form-urlencoded body to JSON and back in the browser. Repeated keys become arrays, and a plus sign is a space.
- HTTP Response Headers Explainer: Paste response headers and get each one explained — plus warnings for missing security headers like HSTS and CSP.
Want me to talk about your product? You can sponsor this site.