How HTTP Works

Request and response header

< Content-Type: application/json; charset=utf-8

Content-Type Header: Values, Examples, charset

Content-Type tells the receiver what the body is: application/json, text/html; charset=utf-8, multipart/form-data. Common values, examples and 415 fixes.

Reviewed 3 min readbeginner5 sourcesTry itMarkdown
Direction
Request and response
Category
Representation
Spec
RFC 9110 §8.3
JS can set it
Yes
CORS-safelisted
Only some values
On this page

TL;DR: Content-Type names the media type of the body. Send application/json with JSON, let your HTTP client set the multipart/form-data boundary, and add charset=utf-8 to text/* types; wrong or missing values cause 415 errors and blocked scripts.

Syntax

Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx3ZQ

A media type is type/subtype, optionally followed by ; parameter=value. Types are case-insensitive. charset is meaningful for text/* types; boundary is required for multipart/*.

Common values

Content-TypeUsed for
text/html; charset=utf-8Web pages
text/plain; charset=utf-8Plain text
text/css, text/javascriptStylesheets and scripts (RFC 9239 makes text/javascript the one to use)
application/jsonJSON APIs
application/problem+jsonRFC 9457 error bodies
application/x-www-form-urlencodedDefault HTML form posts
multipart/form-dataFile uploads
application/octet-streamUnknown binary; browsers download
application/pdf, application/zipDocuments and archives
image/png, image/webp, image/svg+xml, video/mp4Media

Requests

POST /api/users HTTP/1.1
Content-Type: application/json

{"name":"Alice"}

With fetch and FormData, do not set the header yourself. The browser adds the boundary:

// Right: browser sets multipart/form-data; boundary=...
fetch('/upload', { method: 'POST', body: formData })

// Wrong: the boundary is missing and the server cannot split the parts
fetch('/upload', { method: 'POST', body: formData, headers: { 'Content-Type': 'multipart/form-data' } })

With curl, -d defaults to application/x-www-form-urlencoded, which is why JSON posts fail until you add -H 'Content-Type: application/json':

curl -i https://api.example.com/users -d '{"name":"Alice"}'
# HTTP/1.1 415 Unsupported Media Type  (or 400 with an empty body)
curl -i https://api.example.com/users -H 'Content-Type: application/json' -d '{"name":"Alice"}'

One CORS note: Content-Type: application/json is not a safelisted value for cross-origin requests, so it triggers a preflight. Only application/x-www-form-urlencoded, multipart/form-data and text/plain are exempt. See CORS preflight.

Errors people search for

Refused to execute script from 'https://example.com/app.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled. The path returned your SPA fallback or a 404 page. Check the actual response with curl -sI, fix the asset path or try_files/rewrite rule, and keep X-Content-Type-Options: nosniff on.

Refused to apply style from '...' because its MIME type ('text/html') is not a supported stylesheet MIME type. Same cause for CSS.

Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "application/octet-stream". Serve .js and .mjs as text/javascript. In nginx check include mime.types; is present; in S3 or GCS set the object metadata Content-Type.

415 Unsupported Media Type. The server did not accept the request body type. See 415.

Mojibake such as é. A text/html response without charset is guessed by the browser. Send charset=utf-8 and also <meta charset="utf-8"> in the first 1024 bytes.

Download instead of render. application/octet-stream or a missing type with Content-Disposition: attachment. See Content-Disposition.

Sniffing

If the type is missing or generic, browsers may guess from the content (MIME sniffing), a vector for XSS through uploaded files served as HTML. X-Content-Type-Options: nosniff disables sniffing for scripts and styles and is worth setting everywhere; see X-Content-Type-Options. Serve user uploads from a separate domain with the correct type, or with Content-Disposition: attachment.

Server configuration

nginx maps extensions in mime.types and falls back to default_type:

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

Apache: AddType text/javascript .mjs. Verify with:

curl -sI https://example.com/app.mjs | grep -i content-type

In practice

Express.js
// JSON: res.json() sets Content-Type automatically
app.get('/api/users', (req, res) => {
  res.json({ users: [] })
  // Content-Type: application/json; charset=utf-8
})

// Parse only the types you expect; others skip the body parser
app.use(express.json({ type: 'application/json' }))

// File download
app.get('/report', (req, res) => {
  res.set('Content-Type', 'application/pdf')
  res.set('Content-Disposition', 'attachment; filename="report.pdf"')
  res.send(pdfBuffer)
})
Next.js App Router
// Response.json() sets Content-Type: application/json
export async function GET() {
  return Response.json({ users: [] })
}

// Custom type
export async function GET() {
  return new Response('<feed></feed>', {
    headers: { 'Content-Type': 'application/atom+xml; charset=utf-8' }
  })
}
Django
from django.http import JsonResponse, HttpResponse

def api_view(request):
    return JsonResponse({'users': []})  # application/json

def feed_view(request):
    return HttpResponse('<feed></feed>', content_type='application/atom+xml; charset=utf-8')

Frequently asked questions

What is the Content-Type header?

It names the media type of the message body, optionally with parameters such as charset or boundary, so the receiver knows how to parse it. In a request it describes what you send; in a response it describes what the server returns.

What Content-Type should I use for JSON APIs?

Use application/json. RFC 8259 defines no charset parameter for it because JSON is always UTF-8 on the wire, so adding charset=utf-8 is harmless but unnecessary. Frameworks that emit it, such as Express res.json(), are not wrong.

What is the difference between application/x-www-form-urlencoded and multipart/form-data?

urlencoded sends key=value pairs with percent-encoding and is the default for HTML forms. multipart/form-data splits the body into parts separated by a boundary and is required for file uploads. Use a library to build multipart bodies: the Content-Type must include the generated boundary parameter.

Why does my server return 415 Unsupported Media Type?

The request Content-Type is not one the endpoint accepts. Typical causes are posting JSON without Content-Type: application/json, sending text/plain, or forcing a manual multipart header that lacks the boundary. See the 415 page for fixes.

Why does the browser say "Refused to execute script ... MIME type text/html"?

The script URL returned HTML, usually a 404 page or SPA fallback index.html, and the response carried X-Content-Type-Options: nosniff, so the browser refuses to treat it as JavaScript. Fix the path or the server rewrite rule so the file is actually served with a JavaScript type.

Should I use application/javascript or text/javascript?

Use text/javascript. RFC 9239 made it the sole recommended type and obsoleted the application/javascript preference; browsers accept both.

Sources

  1. MDN Web Docs: Content-Typedeveloper.mozilla.org
  2. RFC 9110 section 8.3: Content-Typerfc-editor.org
  3. RFC 8259: JSON (media type and encoding)rfc-editor.org
  4. RFC 9239: Updated Specification of Text Media Types for JavaScriptrfc-editor.org
  5. IANA Media Types Registryiana.org

Keep going

Browse /search