Access-Control-Request-Headers: declaring custom headers
Access-Control-Request-Headers lists the non-safelisted headers the browser intends to send, so the server can approve them before the real request goes out.
At a glance
- Direction
- Request only, on the preflight, and set by the browser.
- Defined in
- The Fetch standard (WHATWG)
- When absent
- The upcoming request uses only safelisted headers, and the preflight is about the method alone.
- Formatting
- Names are lowercased and sorted by the browser before sending.
- Omits
- Safelisted headers such as Accept and Accept-Language, which need no permission
- Answered by
- Access-Control-Allow-Headers on the preflight response
- Includes
- Content-Type, whenever its value is outside the safelisted set
In detail
The browser gathers every header the upcoming request will carry that is not already safelisted, lowercases the names, sorts them and sends the list. That normalisation is why your carefully cased X-Request-Id arrives as x-request-id: it is a property of the preflight format, not a sign that something rewrote your code.
The list is also the fastest way to find out why a preflight is happening at all. If it contains only content-type, the trigger is a JSON or other non-form body rather than any custom header, and removing custom headers will not avoid the extra round trip. If it contains authorization, no wildcard answer will satisfy it.
Your response does not have to match the formatting, only the content. Header names are compared case insensitively, so answering with Authorization, Content-Type is fine against a request for authorization,content-type. Every name in the request list must appear in your answer, and a single omission fails the whole preflight.
Logging this header on your OPTIONS handler is worth doing while integrating a new client. It tells you exactly what the browser will send before the real request exists, which is far more reliable than reading through client code trying to work out which library added which header, and it survives the minified bundles where that reading is hardest.
It never includes headers the browser sets itself, such as Origin, Referer or the Sec-Fetch set, because those are on the forbidden list and page code cannot add them. So a name appearing here always came from your own client code or from a library it loaded, which narrows the search when an unexpected one shows up.
Examples
OPTIONS /api/items HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type,x-request-idHTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Authorization, Content-Type, X-Request-IdThings that catch people out
- Answering with a wildcard does not cover authorization, so a list containing that name needs an explicit answer.
- The names arrive lowercase because the browser normalises them, which is not a signal that anything is wrong.
- Adding one new header to a client forces a fresh preflight even for a URL whose permissions were already cached.
Try it: HTTP Headers Checker
Check a live URL or paste headers you already have. It grades six security headers from A+ to F and lists every other header the response returned.
HTTP Headers Checker
LOCAL · Browser-OnlyView HTTP response headers for any URL
Enter any website URL to fetch and analyze its HTTP response headers. We'll automatically add https:// if needed.
Why Check Security Headers?
Security headers protect your website against common attacks like XSS, clickjacking, and MIME sniffing. They're set by your web server and cost nothing to implement. Missing security headers are one of the most common findings in security audits.
Related HTTP headers
All http headers →Access-Control-Allow-Headers: permitted request headers
Which request headers trigger a preflight, how to answer with the right allow list, and the one header a wildcard never covers.
Access-Control-Request-Method: the preflight question
What the browser sends on a preflight, which methods trigger one, why the server must answer with an exact match, and how to reproduce a preflight by hand.
Authorization: credentials on a request
How the Authorization header is structured, how Basic and Bearer differ, why it is dropped across a cross-host redirect, and how it interacts with caching.
Content-Type: media type, charset and boundary
How Content-Type is structured, why the charset parameter still matters, what boundary does on a multipart upload, and how a wrong type breaks a page.
Timing-Allow-Origin: exposing detailed resource timings
Why cross-origin resources show zeroed timings, what Timing-Allow-Origin unlocks in the Resource Timing API, and how it differs from a CORS allow header.
WWW-Authenticate: the challenge that comes with a 401
What a 401 challenge must contain, what realm means to a browser, how Bearer error codes are reported, and why a 401 without this header is malformed.
Tools for this
- HTTP Request Builder Free online HTTP request builder - build and test HTTP requests with custom headers and body
- HTTP Headers Checker Free online HTTP headers checker - view HTTP response headers for any URL