Inspecting Preflight in the DevTools Network Panel
The browser Network panel is the fastest place to confirm whether a cross-origin request triggered a CORS preflight and, if so, exactly which header the server refused. Yet the panel also hides the one thing you most want to see: when a request is blocked, DevTools frequently shows only “provisional headers” instead of the real response, sending developers on a hunt for a server bug that the panel simply failed to render.
This page is part of Cross-Origin Debugging & Error Diagnosis, which covers reading browser errors, network inspection, and command-line reproduction of CORS failures.
What the Network Panel Shows for a Preflighted Request
The WHATWG Fetch Standard (§4.8, CORS-preflight fetch) defines a preflight as a separate OPTIONS request the browser sends before a non-simple request. In the Network panel this appears as two distinct rows to the same URL: the OPTIONS preflight, then the actual GET, POST, PUT, or DELETE once the preflight succeeds. Reading a CORS problem correctly means reading both rows as a pair — the request headers on the preflight declare intent, and the response headers declare what the server allowed.
The diagram below shows the two-request sequence the panel records when a preflighted request succeeds.
Reference: Where Each Header Appears in the Panel
| Header | Direction | Pane in DevTools | What it tells you |
|---|---|---|---|
Origin |
Request | Request Headers | The origin the browser attributed to the caller |
Access-Control-Request-Method |
Request | Request Headers | The method the actual request will use |
Access-Control-Request-Headers |
Request | Request Headers | The non-simple headers the actual request will send |
Access-Control-Allow-Origin |
Response | Response Headers | The origin the server grants; must match Origin or be * |
Access-Control-Allow-Methods |
Response | Response Headers | Methods the server permits; must include the requested method |
Access-Control-Allow-Headers |
Response | Response Headers | Headers the server permits; must cover every requested header |
Access-Control-Max-Age |
Response | Response Headers | How long the browser caches this preflight result |
Access-Control-Allow-Credentials |
Response | Response Headers | Whether cookies/Authorization may accompany the actual request |
Step-by-Step: Isolate and Read the Preflight
Step 1 — Enable Preserve Log Before Reproducing
A failed cross-origin request often coincides with a page reload or a single-page-app route change that clears the Network list. In Chrome DevTools, tick Preserve log at the top of the Network tab; in Firefox, click the gear icon and enable Persist Logs. Without this, the OPTIONS row can vanish before you inspect it.
Step 2 — Filter to the OPTIONS Request
Chrome exposes a method filter: type method:OPTIONS in the filter box to show only preflight rows. Firefox has no method token, so filter by the endpoint path or click the XHR/Fetch category, then sort by the Method column. Isolating OPTIONS removes the noise of unrelated document, script, and image requests.
Step 3 — Read the Request Side
Select the OPTIONS row and open the Headers pane. Under Request Headers, confirm the browser sent Origin, Access-Control-Request-Method, and — when your client adds custom headers — Access-Control-Request-Headers. These are generated by the browser, not your code; they are the browser’s declaration of what it is about to do.
Step 4 — Read the Response Side and Compare
Still in the Headers pane, scroll to Response Headers. Line up each request value against its permission:
Access-Control-Request-Method: POSTmust be covered byAccess-Control-Allow-Methods.- Every token in
Access-Control-Request-Headersmust be covered byAccess-Control-Allow-Headers. Originmust equalAccess-Control-Allow-Origin(or the server returns*for non-credentialed requests).
A single missing token is the whole bug. The detailed walkthrough of this comparison lives in Reading a Preflight OPTIONS Request in DevTools.
Step 5 — Recognise the Provisional-Headers Gotcha
When a request is blocked, Chrome shows “Provisional headers are shown” with a caution icon, and the Response tab is empty. This does not mean the server sent nothing — it means the browser discarded the response before DevTools captured its headers. The panel is now blind to the truth. Switch to the command line: reproduce the exact request with curl to read the real response headers the server returned.
Edge Cases and Reading Pitfalls
The Response Tab Is Empty on a Blocked Request
For a CORS-blocked response, the browser withholds the body from the page, and DevTools mirrors that: the Response and Preview tabs are blank even though the server may have returned a full JSON body. An empty Response tab is expected on a blocked request and is not evidence of a server-side empty response.
A Cached Preflight Shows No OPTIONS Row
If a prior preflight returned Access-Control-Max-Age, the browser caches the decision and skips the OPTIONS request for subsequent calls within the TTL. Seeing only the actual request, with no OPTIONS row, can mean the preflight was cached — not that it never happened. Disable cache (the Disable cache checkbox) to force a fresh preflight while debugging. See how to set Access-Control-Max-Age effectively for how this TTL behaves.
The Timing Pane Isolates Preflight Cost
The Timing pane on the OPTIONS row shows the round-trip the preflight adds before the actual request can start. On a distant origin this latency is visible and measurable; if it dominates, the fix is caching or reducing preflights, not header edits.
null Origin and Sandboxed Contexts
Requests from sandboxed iframes, data: URLs, or some redirect chains carry Origin: null. In the panel this looks like a literal null string in the Request Headers, and the server must not blindly reflect it. Origin evaluation is covered in how browsers evaluate the same-origin policy.
Verification Checklist
Common Mistakes
| Issue | Technical impact | Mitigation |
|---|---|---|
| Trusting “Provisional headers are shown” as the server response | Developer chases a nonexistent server bug; the real headers were never rendered | Reproduce with curl -i to read the actual response headers |
| Reading an empty Response tab as an empty server body | Misdiagnosis; the body was withheld by the browser, not absent | Confirm the body with curl outside the browser’s CORS enforcement |
| Not enabling Preserve log | The OPTIONS row disappears on reload before it can be inspected |
Enable Preserve log / Persist Logs before triggering the request |
Assuming no OPTIONS row means no preflight |
A cached preflight is skipped within its Access-Control-Max-Age TTL |
Tick Disable cache to force a fresh preflight |
| Comparing only the response headers | Missing the request-side declaration that defines what must be allowed | Read Access-Control-Request-* and Access-Control-Allow-* as a pair |
FAQ
Why don’t I see an OPTIONS request in the Network panel?
Two reasons are common. First, the request may be a simple request that never triggers a preflight, so no OPTIONS row exists. Second, the panel may have cleared on navigation; enable Preserve log so entries survive the page reload the failed request often causes. If a preflight was cached via Access-Control-Max-Age, the browser skips it entirely and reuses the prior result.
What does ‘Provisional headers are shown’ mean on a CORS request?
It means the browser blocked the response before the real response headers were parsed, so DevTools can only display the tentative request headers it prepared, not what the server actually returned. It is a symptom of a failed or blocked request, not the cause. Reproduce the same request with curl to read the true response headers the server sent.
Should I read the request or the response headers to debug a preflight?
Both, as a pair. The Access-Control-Request-Method and Access-Control-Request-Headers request headers state what the browser intends to do. The Access-Control-Allow-Methods and Access-Control-Allow-Headers response headers state what the server permits. A preflight fails when a requested value is absent from the matching allow list, so you compare the two sides.
Does the Network panel show the preflight and the actual request as one row or two?
Two rows to the same URL: an OPTIONS entry for the preflight, followed by the actual GET, POST, PUT, or DELETE entry once the preflight passes. If the preflight fails, only the OPTIONS row appears and the actual request is never sent, because the browser aborts before it.
Related
- Cross-Origin Debugging & Error Diagnosis — parent topic
- Reading a Preflight OPTIONS Request in DevTools — step-by-step read of a single preflight row
- Decoding Browser CORS Error Messages — mapping console text to causes
- Reproducing CORS Failures with curl — read the true server response the panel hides
- Simple vs Preflight Requests — when an OPTIONS preflight fires at all