CORS Preflight Simulator
A CORS preflight is an automatic OPTIONS request the browser sends before a
cross-origin call to ask the server whether the real request is allowed. It fires only for
"non-simple" requests, such as a custom header or a PUT. Pick the request shape
on the left, configure the server's CORS headers on the right, and watch the
preflight appear — or not — and the real request succeed or fail.
The left panel is the request your JavaScript is about to make — method, Content-Type, custom headers, credentials. The right panel is the CORS headers the server answers with. Between them, the simulator shows what the browser decides to do: whether it silently sends an OPTIONS preflight first, and whether the real request is allowed through. The two counters above track exactly that — preflight yes or no, final OK or blocked.
Start with a plain GET and text/plain: no preflight, one request. Now flip the Content-Type to application/json and watch an OPTIONS request appear that your code never asked for. Then break things: switch the method to PUT while Allow-Methods only lists GET and POST, or turn on credentials while Allow-Origin is *. What should surprise you is who does the blocking. The server never refuses anything — it answers happily. The browser is the enforcer, and a "blocked" simple request may still have reached the server and run; the browser just refuses to show your JavaScript the response. That one detail explains most confusing CORS bugs.
Why CORS exists
The Same-Origin Policy is the default; CORS is the opt-in.
Without the browser's Same-Origin Policy, any
website you visit could send authenticated requests to any other
site you happen to be logged in to. Visit a malicious page; it
fires a fetch('https://bank.example.com/transfer?to=attacker');
your browser sends your bank cookies along; the bank processes the
transfer. The Same-Origin Policy blocks this by refusing to let
one origin read responses from another.
CORS (Cross-Origin Resource Sharing) is the opt-in escape hatch. The server says, in its response headers, "yes, this origin is allowed to read my responses". The browser checks those headers and either delivers the response to the page or hides it and shows the famous CORS error.
The preflight exists because some requests would have side effects on the server even if the browser blocked the response. A POST that creates a record happens on the server even if the browser then refuses to read the response. So for "non-simple" requests, the browser fires a preliminary OPTIONS request asking "would you accept this request?" — and only fires the real one if the server says yes.
Four mistakes that cost everyone 30 minutes once
Memorise these and never debug CORS again.
- Sending JSON triggers a preflight.
Content-Type: application/jsonisn't in the simple-CT list. Switch totext/plainand the preflight disappears — but you usually want JSON, so configure the server to permit it viaAccess-Control-Allow-Headers: content-type. - Wildcard origin and credentials don't mix. The spec forbids
Access-Control-Allow-Origin: *together withcredentials: 'include'. Pick a specific origin and echo it back from the server based on the request's Origin header. - Forgetting Access-Control-Allow-Credentials. Setting
credentials: 'include'on the client is half the story; the server must also sendAccess-Control-Allow-Credentials: trueon both the preflight response AND the real-request response. - Missing Vary: Origin. If your server echoes the request Origin into Access-Control-Allow-Origin (the common pattern), include
Vary: Originin the response. Without it, a CDN can cache one origin's response and serve it to other origins, breaking the world.