Reading a Preflight OPTIONS Request in DevTools
You opened the Network tab to debug a cross-origin call and hit one of two walls: there is no OPTIONS request anywhere in the list, or there is one and it is highlighted red with a status that looks fine. This page shows how to surface the preflight row and read it so the failing header is obvious.
This page is part of Inspecting Preflight in the DevTools Network Panel, which covers the panel end to end.
The Symptom
Access to fetch at 'https://api.example.com/v1/orders' from origin
'https://app.example.com' has been blocked by CORS policy: Response to
preflight request doesn't pass access control check: Request header field
authorization is not allowed by Access-Control-Allow-Headers in preflight response.
In the Network tab this pairs with either a missing OPTIONS row (you scrolled past it, or the log cleared) or a red OPTIONS row whose status line reads 204 — which looks successful but is not, because the response is missing a header the browser required.
Root Cause
The WHATWG Fetch Standard (§4.8) requires the preflight response to explicitly list every non-simple request header in Access-Control-Allow-Headers and the intended method in Access-Control-Allow-Methods. The browser performs an exact, case-insensitive token comparison between what it declared in Access-Control-Request-Method/Access-Control-Request-Headers and what the server allowed. If a single declared token is absent from the corresponding allow list, the CORS-preflight fetch returns a failure and the actual request is never dispatched — regardless of the 204/200 status. The status code describes the HTTP transaction; the headers decide the CORS outcome.
Prerequisite State
- DevTools is open on the page making the request, with the Network tab selected.
- You can re-trigger the failing request (a button click, a reload, or a route change).
- You know the endpoint URL and the method your client intends (
POST,PUT,DELETE, etc.).
Step-by-Step Fix
Step 1 — Enable Preserve Log
The failed request commonly rides on a navigation that wipes the Network list. Turn on Preserve log (Chrome) or Persist Logs (Firefox, via the gear icon) so the OPTIONS row is retained.
Step 2 — Filter to the Preflight
In Chrome, type a method filter into the filter box:
method:OPTIONS
Firefox lacks a method token, so type the endpoint path fragment (for example orders) into the filter box and read the Method column to find the OPTIONS entry.
Step 3 — Open the OPTIONS Row’s Headers Pane
Click the OPTIONS entry and select the Headers pane (not Response — a preflight body is empty by design). You will read two sections: Request Headers and Response Headers.
Step 4 — Read the Request Declaration
Under Request Headers, note the browser’s declaration:
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
This says: “I am about to send a POST carrying authorization and content-type headers.”
Step 5 — Read the Server’s Permission and Find the Gap
Under Response Headers, read what the server allowed:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: content-type
Compare token by token. POST is present in Access-Control-Allow-Methods — good. But authorization from the request is absent from Access-Control-Allow-Headers, which lists only content-type. That single missing token is the failure. The fix is server-side: add authorization to the allow list, as covered in Access-Control header directives.
The diagram below shows this side-by-side comparison.
Verification
Confirm the server now allows the header from the command line, outside the browser:
curl -si -X OPTIONS https://api.example.com/v1/orders \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: authorization, content-type" \
| grep -i "access-control-allow-headers"
The output must list authorization:
access-control-allow-headers: authorization, content-type
Then re-run the request in the browser: the OPTIONS row should no longer be red, and the actual POST row should now appear beneath it. The full command-line technique is in Simulating a Preflight with curl -X OPTIONS.
Security Boundary Note
Do not “fix” a missing-header preflight by reflecting the request’s headers back wholesale — for example echoing Access-Control-Request-Headers straight into Access-Control-Allow-Headers without an allow list. Blind reflection means any header a caller names is automatically permitted, which erodes the server’s control over what it accepts. Maintain an explicit allow list and add only the specific headers your API legitimately consumes.
Common Mistakes
| Mistake | Technical impact | Fix |
|---|---|---|
Reading the OPTIONS status 204 as success |
The preflight fails on a missing header despite a 2xx status | Compare Access-Control-Request-* against Access-Control-Allow-*, not the status code |
| Looking in the Response tab for the answer | A preflight body is empty; the tab shows nothing useful | Read the Headers pane’s Response Headers section |
| Filtering the whole list and missing the row | The OPTIONS entry is buried among document and asset requests |
Use method:OPTIONS (Chrome) or filter by the endpoint path |
FAQ
The OPTIONS row is red. Does that mean the server is broken?
Not necessarily. A red OPTIONS row means the browser judged the preflight response insufficient for the actual request it wanted to make. The server may have returned a valid 200 or 204 that simply omits a required Access-Control header. Open the row and compare the request’s Access-Control-Request values against the response’s Access-Control-Allow values to see which one is missing.
Why does the Response tab of the OPTIONS request show nothing?
A preflight legitimately has an empty body, so an empty Response tab is normal for OPTIONS. The information you need is in the Headers pane, not the Response tab. Read the Response Headers section for the Access-Control-Allow directives.
Related
- Inspecting Preflight in the DevTools Network Panel — parent: the full Network-panel workflow
- Simulating a Preflight with curl -X OPTIONS — reproduce the same preflight on the command line
- Debugging Missing Access-Control-Allow-Origin Header — when the origin header itself is absent