How HTTP Works

Response header

< Content-Location: /documents/42

Content-Location Header

Learn how Content-Location indicates an alternate URL for returned content. Useful for content negotiation and identifying canonical resource locations.

Reviewed 5 min readadvanced2 sourcesTry itMarkdown
Direction
Response
Category
Representation
Spec
RFC 9110 §8.7
On this page

TL;DR: Content-Location tells the client the direct URL of what it just received. If /report negotiates JSON, the server can send Content-Location: /report.json. It’s metadata only: no redirect, no canonical-URL declaration, no extra cache entry.

What is Content-Location?

Its main job is naming the variant a server picked: a request to /report might negotiate JSON and come back with Content-Location: /report.json. It describes the response body; the request URL stays what it was. See RFC 9110 §8.7.

How Content-Location Works

Requests and responses on this page are examples, with bodies omitted:

GET /report HTTP/1.1
Host: example.com
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
Content-Location: /report.json
Vary: Accept

The address bar stays at /report. The server should also serve this representation at /report.json; the header is a pointer, so you still have to build the route.

Syntax

Content-Location: /report.json
Content-Location: report.json
Content-Location: https://example.com/report.json

Relative values resolve against the request’s target URI. A leading slash starts at the origin root; report.json resolves relative to the target path.

For a response to https://example.com/reports/current, summary.json resolves to https://example.com/reports/summary.json; /summary.json resolves to https://example.com/summary.json. Resolve it against the request URL, not against the page that made the fetch.

It also leaves the base URL for relative links inside an HTML document alone. Naming the representation and resolving the document’s links are separate things.

Common Examples

A negotiated /welcome response might point to /welcome.fr.html. A /posts/latest response might point to the specific post it returned. The content at either URL may change later.

Request targetPossible returned fieldMeaning
/welcome/welcome.fr.htmlSelected French representation
/posts/latest/posts/42Post currently returned by the alias
/report/report.jsonSelected JSON representation

The alias and the post it points to live on different timelines. Publish a new post and /posts/latest returns that one, while /posts/42 keeps naming the old one. And /posts/42 itself can be edited, unless your application promises otherwise.

Real-World Scenarios

In a 201 Created response, Location identifies the created resource. If Content-Location has the same value, the response body is a current representation of that resource. If it differs, the body might be a status report about the operation instead. MDN illustrates the distinction.

Here the body of a creation response is the new report itself:

HTTP/1.1 201 Created
Location: /reports/42
Content-Location: /reports/42
Content-Type: application/json

A purchase that returns a receipt is different: the created order and the receipt in the body have their own URLs.

HTTP/1.1 201 Created
Location: /orders/42
Content-Location: /receipts/42
Content-Type: application/json

Clients should go by the API contract when choosing which URI to fetch. A creation response’s body might be the new object, or it might be something else entirely.

Server Implementation

These Express routes serve both the negotiation endpoint and its identified variant:

const report = { status: 'ready' }
app.get('/report.json', (req, res) => res.json(report))
app.get('/report', (req, res) => {
  res.vary('Accept')
  if (!req.accepts('application/json')) return res.status(406).end()
  res.set('Content-Location', '/report.json')
  res.json(report)
})

Set headers before res.json() sends the response. Express documents req.accepts() and response methods.

A FastAPI application can return an alias and a direct endpoint for the same fixture:

from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()
post = {'id': 42, 'title': 'Release notes'}

@app.get('/posts/42')
def specific_post():
    return post

@app.get('/posts/latest')
def latest_post():
    return JSONResponse(post, headers={'Content-Location': '/posts/42'})

When returning a response object directly, put headers on that object. FastAPI distinguishes this from setting headers on its injected temporary Response. The alias is a real endpoint that returns data, not a redirect. In a real app, swap the fixture for the database record you selected, and make sure the direct endpoint enforces the same access rules.

For a fixed Django alias, set the representation identifier on the returned object:

from django.http import JsonResponse

def latest_post(request):
    response = JsonResponse({'id': 42, 'title': 'Release notes'})
    response['Content-Location'] = '/posts/42'
    return response

Map /posts/42 to a view that returns the post too. The header won’t register a URL pattern for you.

Getting Content-Location right

A shared cache stores the /report response under /report, not under /report.json as well. RFC 9111 §2 bases cache keys on the request method and target URI, with negotiated variants differentiated using Vary. If you want aliasing, configure it on your cache and test it.

Be careful with a value pointing at another origin: the server sending it may not control that URL. RFC 9110 §8.7 warns that a client can’t work out ownership from the field alone.

Invalidation works differently again. After a successful unsafe request such as POST, a cache must invalidate the request target and may invalidate same-origin URIs named in Location or Content-Location. It must not invalidate URIs on another origin this way. RFC 9111 §4.4 has the rules. So naming /receipts/42 in a POST response is a hint, and some caches will keep serving their old copy of the receipt.

Content-Location vs Location

Location supplies a redirect target or identifies a newly created resource. Content-Location describes the enclosed representation. Both can appear on a 201; Content-Location is not restricted to 200 responses.

Common Use Cases

Use the field when a negotiated variant or an operation’s returned report has a useful retrieval URL. For a redirect, send the appropriate 3xx status and Location instead.

Clients that send it on requests hit a gotcha. PUT /report with Content-Location: /report.fr.html is still a PUT to /report, not an update to just the French variant; RFC 9110 §8.7 requires the server to keep the original request semantics. To update one variant, send the PUT to that variant’s URI. On the server, treat an incoming Content-Location as context, never as a new target.

Testing Content-Location

curl -sS -D - -o /dev/null -H 'Accept: application/json' https://example.com/report
curl -sS -D - -o /dev/null https://example.com/report.json

Check that the variant endpoint exists and returns the same representation. curl -L follows redirects only; it won’t fetch the Content-Location URL for you.

In a same-origin browser client, inspect and resolve the field explicitly:

const response = await fetch('/report', {
  headers: { Accept: 'application/json' }
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const value = response.headers.get('Content-Location')
if (value) {
  const representation = new URL(value, response.url)
  console.log(representation.href)
}

response.url supplies the final response URL, including redirects. Logging the URL has no side effects: no navigation, no caching. If you do fetch it, check the destination against your application’s rules first. Cross-origin JavaScript needs the server to expose Content-Location through CORS before it can read this field.

Frequently asked questions

What is Content-Location?

Content-Location gives the direct URL of the representation in the response. After content negotiation, it tells the client where it can fetch that specific variant directly.

What is the difference between Content-Location and Location?

Location points to a redirect target, or to the resource a 201 response just created. Content-Location names the resource that the body of this response represents.

When should I use Content-Location?

Use it after content negotiation to show the direct URL for the selected variant. For example, if /doc returns HTML, Content-Location: /doc.html shows the specific version.

Does Content-Location affect caching?

Not as a cache key. Caches key responses on the request method and target URI, and use Vary for negotiated request fields, so a response to /report is not also stored under its Content-Location URL.

Sources

  1. MDN Web Docs: Content-Locationdeveloper.mozilla.org
  2. RFC 9110: Content-Locationrfc-editor.org

Keep going

Browse /search