Content-Type and Accept are two of the most commonly confused HTTP headers. They look similar, often have the same value (application/json), and both deal with data formats — but they serve completely different purposes. Confusing them causes broken API requests, 400 errors, and hours of debugging. This guide clarifies exactly what each header does, when to use them, and how they work together in HTTP content negotiation.
The One-Sentence Difference
📤
Content-Type
"Here is the format of the data I am sending you"
📥
Accept
"Here is the format I want to receive back"
Real-World Analogy: The Restaurant
Imagine ordering at a restaurant:
You (client) tell the waiter: "I'd like my meal served on a plate" → This is the Accept header (what format you want to receive)
You (client) hand the waiter a written note with your order → The note's format is the Content-Type of your request (what you're sending)
Kitchen (server) prepares the food and serves it on a plate → The plate is the Content-Type of the response (what the server is sending back)
Content-Type vs Accept: Complete Comparison
| Feature | Content-Type | Accept |
|---|---|---|
| Purpose | Describes format of the BODY being sent | Describes format the client wants to RECEIVE |
| Who sends it | Both client (request) and server (response) | Only the client (in request) |
| Direction | Describes current message body | Describes desired response body |
| Required when? | When request/response has a body | Optional (but recommended for APIs) |
| In GET requests? | No (GET has no body) | Yes (tells server what format you want) |
| In POST requests? | Yes (describes the body format) | Yes (tells server what format to return) |
| In responses? | Yes (always describes response body) | No (Accept is only a request header) |
| Default if missing | Server may reject or guess | Server uses its default format |
| Common values | application/json, multipart/form-data | application/json, text/html, */* |
| Browser sets it? | Automatically for forms | Automatically for page loads |
What Is the Content-Type Header?
The Content-Type header describes the format (MIME type) of the data in the HTTP message body. It answers the question: "What format is this data in?" — so the recipient knows how to parse it.
Content-Type is unique because it appears in both requests and responses:
- In requests: Tells the server what format the request body is in. "I'm sending you JSON, so use your JSON parser."
- In responses: Tells the browser/client what format the response body is in. "This response is HTML, so render it as a page."
Without Content-Type, the server has no reliable way to know whether you sent JSON, XML, form data, or a binary file. It would have to guess — and guessing leads to parsing failures. This is why most 400 Bad Request errors in APIs are caused by a missing or incorrect Content-Type header.
Content-Type in Requests
When you send a POST, PUT, or PATCH request with a body, Content-Type tells the server how to read that body. The server's middleware (like Express's express.json()) looks at Content-Type to decide which parser to activate:
// JSON body → server activates JSON parser
Content-Type: application/json
Body: {"name": "Alice", "age": 30}
// Form data → server activates URL-encoded parser
Content-Type: application/x-www-form-urlencoded
Body: name=Alice&age=30
// File upload → server activates multipart parser
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
Body: [binary file data split by boundary]Content-Type in Responses
In server responses, Content-Type tells the browser how to handle what it received. This determines whether the browser renders HTML, parses JSON, displays an image, or triggers a file download:
Content-Type: text/html; charset=utf-8 → Browser renders as web page Content-Type: application/json → Browser/JS parses as JSON object Content-Type: image/png → Browser displays as image Content-Type: application/pdf → Browser opens PDF viewer or downloads Content-Type: text/css → Browser applies as stylesheet Content-Type: application/javascript → Browser executes as script
If Content-Type is wrong in a response, things break visibly. A JSON API that returns Content-Type: text/html will cause fetch().json() to fail. An HTML page served as application/octet-stream will trigger a download instead of rendering.
What Is the Accept Header?
The Accept header is a request-only header that tells the server what response format the client prefers. It answers: "When you send me the response, please use this format."
Unlike Content-Type (which describes data already being sent), Accept describes data the client wants to receive in the future — it's a preference, not a description of current content.
Accept is critical for APIs that support multiple response formats. A single endpoint like /api/users might return JSON (for JavaScript clients), XML (for legacy systems), or CSV (for spreadsheet exports) — the Accept header determines which format the server chooses.
Accept with Quality Values
The Accept header supports quality values (q=0 to q=1) that indicate preference order. Higher q values mean higher preference. No q value means q=1 (highest):
// "I prefer JSON, but I'll accept HTML or plain text" Accept: application/json, text/html;q=0.9, text/plain;q=0.8 // Breakdown: // application/json → q=1.0 (default, highest priority) // text/html → q=0.9 (second choice) // text/plain → q=0.8 (third choice) // "I accept anything" Accept: */* // "I want images, preferring PNG over JPEG" Accept: image/png, image/jpeg;q=0.8, image/*;q=0.5
What Browsers Send Automatically
Browsers set Accept headers automatically based on the type of request:
text/html, application/xhtml+xml, */*;q=0.8text/css, */*;q=0.1*/*image/avif, image/webp, image/png, */*;q=0.8*/* (accept anything)*/*Notice that browsers prefer modern image formats (avif, webp) over older ones. This is content negotiation in action — the server can return WebP to modern browsers while serving PNG to older ones, all from the same URL.
Content-Type in File Uploads
File uploads are where Content-Type gets tricky. The most common mistake developers make is manually setting Content-Type for FormData — which actually breaks the upload.
How multipart/form-data Works
When you upload a file, the request body is split into multiple "parts" separated by a unique boundary string. Each part has its own Content-Type (the file's type) and Content-Disposition (the field name). The overall request Content-Type must include this boundary for the server to know where each part begins and ends.
// What the browser actually sends for a file upload: POST /api/upload HTTP/1.1 Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="name" Alice ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="avatar"; filename="photo.jpg" Content-Type: image/jpeg [binary JPEG data here] ------WebKitFormBoundary7MA4YWxkTrZu0gW--
The boundary is randomly generated by the browser. If you manually set Content-Type: multipart/form-data without the boundary, the server cannot parse the parts and the upload fails silently. This is why you should never set Content-Type manually when using FormData — let the browser handle it.
⚠️ Pro Tip
When using fetch() with FormData as the body, do NOT set Content-Type in headers. The browser will set it automatically with the correct boundary. If you set it manually, you break the upload.
Common MIME Types
Both headers use MIME types as values. Here are the ones you'll encounter daily:
| MIME Type | Used For | Context |
|---|---|---|
| application/json | JSON data | REST APIs, fetch, Axios |
| text/html | HTML pages | Browser page loads, server-rendered apps |
| text/plain | Plain text | Simple text responses, logs |
| application/xml | XML data | SOAP APIs, legacy systems |
| multipart/form-data | File uploads + form fields | File upload forms |
| application/x-www-form-urlencoded | HTML form data | Default form submission encoding |
| image/png | PNG images | Image responses, uploads |
| image/jpeg | JPEG images | Photo uploads, thumbnails |
| application/pdf | PDF documents | Document downloads |
| application/octet-stream | Binary data (generic) | Any file download |
| text/css | CSS stylesheets | Browser style requests |
| application/javascript | JavaScript files | Browser script requests |
How Content Negotiation Works
Content negotiation is the process where client and server agree on a response format:
1. Client sends Accept header
Accept: application/json, text/html;q=0.9
2. Server checks what it supports
Can produce: JSON ✅ HTML ✅ XML ❌
3. Server responds with best match
Content-Type: application/json (highest quality match)
The q value (0-1) in Accept indicates preference. application/json without q defaults to q=1 (highest priority). text/html;q=0.9 means "I prefer JSON but HTML is acceptable too."
Practical Code Examples
Fetch API — Sending JSON
// POST JSON to an API
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json", // ← I'm SENDING JSON
"Accept": "application/json" // ← I want to RECEIVE JSON
},
body: JSON.stringify({ name: "Alice", email: "alice@example.com" })
})
// GET request — only Accept needed (no body = no Content-Type)
const user = await fetch("https://api.example.com/users/1", {
headers: {
"Accept": "application/json" // ← Only Accept (GET has no body)
// No Content-Type needed — GET requests have no body!
}
})Fetch API — File Upload
// File upload with FormData
const formData = new FormData()
formData.append("avatar", fileInput.files[0])
formData.append("name", "Alice")
const response = await fetch("/api/upload", {
method: "POST",
// ⚠️ Do NOT set Content-Type manually for FormData!
// The browser sets it automatically with the correct boundary
headers: {
"Accept": "application/json" // ← I want JSON response
},
body: formData
})
// ❌ Common mistake:
// headers: { "Content-Type": "multipart/form-data" }
// This BREAKS file uploads — browser needs to set the boundary itselfAxios
// Axios sets Content-Type: application/json automatically for objects
const { data } = await axios.post("/api/users", {
name: "Alice",
email: "alice@example.com"
})
// Axios adds: Content-Type: application/json (automatic)
// Axios adds: Accept: application/json, text/plain, */* (default)
// Explicit headers
const { data } = await axios.get("/api/users/1", {
headers: {
"Accept": "application/json"
}
})cURL
# POST JSON with both headers
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"name": "Alice"}'
# GET with Accept only
curl -H "Accept: application/json" https://api.example.com/users/1
# File upload — curl sets Content-Type automatically with -F
curl -X POST https://api.example.com/upload \
-H "Accept: application/json" \
-F "avatar=@photo.jpg" \
-F "name=Alice"Common Developer Mistakes
⚠️ Setting Content-Type on GET requests
GET requests have no body, so Content-Type is meaningless. Some servers might even reject the request. Only set Content-Type when you have a request body (POST, PUT, PATCH).
Fix: Remove Content-Type from GET and DELETE requests.
⚠️ Manually setting Content-Type: multipart/form-data
When using FormData, the browser automatically sets Content-Type with the correct boundary parameter. Setting it manually omits the boundary, breaking the upload.
Fix: Let the browser/library set Content-Type for FormData automatically.
⚠️ Confusing Accept with Content-Type
Sending 'Accept: application/json' when you mean 'Content-Type: application/json' (or vice versa) causes the server to misinterpret your request.
Fix: Remember: Content-Type = what I'm sending. Accept = what I want back.
⚠️ Sending JSON body without Content-Type: application/json
The server's JSON body parser won't activate without the correct Content-Type. Your request body arrives as undefined/null on the server, causing confusing bugs.
Fix: Always set Content-Type: application/json when sending JSON in the body.
⚠️ Using Accept: */* everywhere
While technically valid, */* gives you no control over response format. The server might return XML, HTML, or any format it prefers. Be specific about what you want.
Fix: Explicitly request the format you expect: Accept: application/json.
⚠️ Not setting Accept on API requests
Some APIs support multiple formats (JSON + XML). Without an explicit Accept header, you might get XML when you expected JSON, breaking your parser.
Fix: Always include Accept: application/json for API requests.
Quick Reference: When to Use Each Header
| Scenario | Content-Type | Accept |
|---|---|---|
| GET /api/users | ❌ Not needed | ✅ application/json |
| POST /api/users (JSON body) | ✅ application/json | ✅ application/json |
| POST /upload (file) | ❌ Browser sets it | ✅ application/json |
| PUT /api/users/1 (update) | ✅ application/json | ✅ application/json |
| DELETE /api/users/1 | ❌ Not needed | Optional |
| HTML form submit | Auto: x-www-form-urlencoded | Auto: text/html |
| GraphQL query | ✅ application/json | ✅ application/json |
| Download PDF | ❌ Not needed (GET) | ✅ application/pdf |
Best Practices
POST, PUT, and PATCH requests need Content-Type so the server knows how to parse the body.
Don't rely on server defaults. Explicitly request application/json to guarantee the response format.
These methods have no body — Content-Type is meaningless and can confuse some servers.
The browser needs to set the multipart boundary. Manual override breaks file uploads.
Use 'Content-Type: text/html; charset=utf-8' to prevent encoding issues.
'Accept: application/json, text/plain;q=0.9' — prefers JSON but accepts plain text as fallback.
Sending form data? Use x-www-form-urlencoded. Sending JSON? Use application/json. Mismatches break parsing.
Axios, Express, and most frameworks set Content-Type automatically for common cases. Don't override unless needed.
Interview Questions
Q: What is the difference between Content-Type and Accept?
A: Content-Type describes the format of the data being sent (request body or response body). Accept describes the format the client wants to receive back. Content-Type = 'what I'm giving you'. Accept = 'what I want back'.
Q: Can Content-Type and Accept have the same value?
A: Yes. When you POST JSON and want JSON back, both are application/json. This is very common in REST APIs. But they serve different purposes — one describes what you send, the other describes what you want.
Q: Which header does the browser send automatically?
A: Browsers set Accept automatically for page loads (text/html), image requests (image/*), and style requests (text/css). For forms, browsers set Content-Type automatically (application/x-www-form-urlencoded or multipart/form-data).
Q: What happens if Content-Type is wrong?
A: The server's body parser won't match the actual data format. Sending JSON with Content-Type: text/plain means the JSON parser never runs — the body arrives as undefined/null. Typically results in 400 Bad Request.
Q: What happens if Accept is missing?
A: The server uses its default response format. For most APIs, that's JSON. But some multi-format APIs might return XML or HTML. It's best practice to always specify Accept explicitly.
Q: Explain content negotiation.
A: Content negotiation is the HTTP mechanism where the client tells the server what formats it accepts (via Accept header with quality values), and the server picks the best matching format it supports and returns it with the appropriate Content-Type response header.
Frequently Asked Questions
What is the Content-Type header?
What is the Accept header?
Can Content-Type and Accept have the same value?
Do GET requests need a Content-Type header?
What happens if Content-Type is wrong?
What happens if Accept header is missing?
What is application/json?
What is multipart/form-data?
What are MIME types?
How does content negotiation work?
How does the browser decide Content-Type?
What is the difference between Content-Type and Accept?
Related Articles & Tools
Conclusion
Content-Type and Accept are two sides of the same coin in HTTP communication. Content-Type describes the format of the data being sent — use it on POST, PUT, and PATCH requests and in every server response. Accept describes the format the client wants to receive — use it on every API request to control the response format.
Together, they enable content negotiation — the mechanism that lets one API serve JSON to mobile apps, HTML to browsers, and XML to legacy systems from the same endpoint. Master these two headers and you eliminate an entire category of API debugging headaches.
Remember the simple rule: Content-Type = "what I'm sending you." Accept = "what I want back."
