CORS Preflight Requests Explained with Real OPTIONS Examples
Reviewed September 3, 2026 · Maintained by William
A CORS preflight is the browser asking a server for permission before it sends certain cross-origin requests. It is not an error by itself and it is not a request your JavaScript normally creates manually.
What a preflight looks like
Imagine a page at https://app.example.com wants to send JSON with an authorization header to https://api.example.net/users. The browser may first send:
OPTIONS /users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
The server can approve that origin, method and header set with a response such as:
HTTP/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
Vary: Origin
Why simple requests sometimes skip preflight
The Fetch standard defines a limited set of “CORS-safelisted” methods and headers. A basic GET or a narrowly shaped form submission may be sent directly. Adding a non-safelisted header, using a method such as PUT, or using many JSON request patterns can trigger the OPTIONS check.
Do not authorize the OPTIONS request and forget the real request
The preflight only tells the browser whether it may continue. Your server must still authenticate and authorize the actual POST/PUT/DELETE request. CORS is a browser access-control protocol, not an authentication system.
Cache carefully
Access-Control-Max-Age can reduce repeated preflights, but browser caps vary. When the allowed origin is selected dynamically, Vary: Origin helps shared caches avoid reusing a response for the wrong origin.
Debugging checklist
- Open the browser network panel and inspect the OPTIONS request separately from the application request.
- Confirm the response allows the exact requesting origin when credentials are involved.
- Confirm the requested method appears in
Access-Control-Allow-Methods. - Confirm non-safelisted request headers are allowed.
- Check that redirects, authentication middleware or error pages are not intercepting OPTIONS.
The CORS Header Generator can help draft a narrow policy, while the HTTP Header Parser helps inspect a copied response. Production behavior still belongs to the server/framework configuration.
References
Walk through a preflight from request to response
Assume a browser page at https://app.example wants to send JSON with an authorization header to https://api.example/orders. Because this is not a CORS-safelisted request, the browser can send a preflight before the application request.
OPTIONS /orders HTTP/1.1
Origin: https://app.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
A narrowly configured API might answer:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Authorization, Content-Type
Vary: Origin
Access-Control-Max-Age: 600
If the browser accepts that preflight, it can proceed with the POST. The actual POST response must still carry the appropriate Access-Control-Allow-Origin header; passing the preflight does not automatically make the final response readable.
Why “it works in curl” is not a CORS test
cURL is not a web browser and does not enforce the browser same-origin policy. A terminal request can return 200 while browser JavaScript is blocked from reading the same response. When debugging CORS, inspect the browser Network panel and distinguish the OPTIONS request from the application request.
Common preflight failures
| Symptom | Likely cause | Check |
|---|---|---|
| OPTIONS returns 404/405 | Router/proxy does not handle OPTIONS | Web server, reverse proxy and framework routes |
| Requested header rejected | Header missing from allow-list | Access-Control-Allow-Headers |
| Method rejected | Method not permitted | Access-Control-Allow-Methods |
| Correct origin sometimes receives wrong cached response | Cache not varying by Origin | Vary: Origin and CDN behavior |
| Preflight passes but JS still fails | Final response lacks CORS header or credentials mismatch | Inspect the POST/GET response too |
Do not turn debugging into a permanent wildcard
A frequent emergency fix is to reflect every incoming Origin or allow broad methods/headers. That can hide the immediate error while widening who may call the API from browser contexts. Prefer an explicit allow-list that matches the environments you operate, and keep development origins separate from production policy.
What to capture in a bug report
Record the page origin, target URL, requested method, requested headers, preflight status, preflight response headers and final response headers. That evidence is much more useful than a screenshot of a generic browser CORS error. The HTTP Header Parser can help format captured headers, while the CORS Header Generator is a starting point for configuration—not a replacement for testing the deployed response.