CORS preflight request sequence
Browser-to-server handshake for cross-origin requests.
A browser security feature sits between your JavaScript and a cross-origin API: CORS
(Cross-Origin Resource Sharing). When your frontend at app.example.com tries to fetch
data from api.example.com, the browser doesn't just send the request — it first asks
permission with an automatic preflight OPTIONS request. Only if the server says yes does
the browser actually send your POST, PUT, or DELETE.
This template shows the handshake: the browser sends an OPTIONS preflight with the origin, requested method, and headers it wants to use. The server responds with an Allow-Origin header (does it trust this origin?) and Allow-Methods (can it use POST?). If both match, the browser unblocks the actual request. If the server says no, the browser blocks the response with a CORS error — and your JavaScript never sees the data.
When to use this template
- API onboarding docs — show frontend engineers how to set the Content-Type header correctly to avoid unexpected preflights, or document where your CORS policy is enforced.
- Security audit — trace the preflight handshake so you can verify your Allow-Origin
and Allow-Methods headers are restrictive (don't allow
*in production). - Debugging CORS errors — when a team reports "CORS error in Chrome, works in Postman", diagram the preflight to pinpoint whether the browser is sending it but the server isn't responding, or the server is allowing it but the allow-list is wrong.
How to adapt it
Customize the headers and policies for your setup:
- Credentials and cookies — add a note that requests with credentials (cookies, auth)
require
Allow-Credentials: trueand must list the origin explicitly (not*). - Multiple allowed origins — expand the "Check origin" step to show a lookup table or environment-based list (dev, staging, production allow different origins).
- Custom headers — add a participant for each custom header your API uses (X-API-Key, X-CSRF-Token) and show them in the Allow-Headers response.
Visual edits regenerate clean code, so you can document your real CORS policy without manual syntax.
Mermaid code
Copy it anywhere Mermaid is supported — GitHub, Notion, or your docs.
sequenceDiagram
participant Browser
participant Server
Browser->>Server: OPTIONS /api/data (preflight)
Note over Browser,Server: No credentials, CORS headers
Server->>Server: Check origin, method, headers
Server->>Browser: 200 OK + CORS headers
Note over Server,Browser: Allow-Origin, Allow-Methods, Allow-Headers
alt CORS allowed
Browser->>Server: POST /api/data (actual request)
Server->>Browser: 200 OK + data
Browser->>Browser: JavaScript receives response
else CORS denied
Browser->>Browser: Block response, throw CORS error
Note over Browser: Origin not in Allow-Origin list
end
Frequently asked questions
- What is a CORS preflight request?
- It's a safety check the browser runs before sending a request that modifies data (POST, PUT, DELETE) or uses custom headers. The browser first sends an OPTIONS request to see if the server allows cross-origin requests from this origin, method, and headers. The server responds with Allow-Origin and Allow-Methods headers. Only if the preflight passes does the browser send the actual request.
- Why does the browser send a preflight at all?
- Old browsers didn't understand CORS — if a webpage could make requests anywhere, a malicious site could submit forms to your bank and steal data. The preflight is a speed bump: the attacker's page must get permission from your server before the request lands. Your server's CORS policy controls who gets in.
- How do I fix CORS errors in my API?
- Add CORS headers to your responses: Access-Control-Allow-Origin (which origins), Access-Control-Allow-Methods (which HTTP verbs), Access-Control-Allow-Headers (which custom headers). Set them on both the preflight OPTIONS response and the actual response. For development, allow localhost:3000; for production, list only your frontend domains. Visual edits let you document your CORS policy without syntax overhead.
- When are preflight requests NOT sent?
- Simple requests (GET, HEAD, POST with standard content-types) skip the preflight. But custom headers, JSON content-type with POST, credentials cookies, or any PUT/DELETE/PATCH always trigger it. If you're not seeing OPTIONS requests in your network tab, you're probably making simple requests — add a custom header to trigger the preflight so you can test your CORS setup.