What is a Preflight Request? Why Browsers Send OPTIONS First
Before sending a cross-origin POST, PUT, DELETE, or any request with custom headers, the browser first asks for permission with an OPTIONS request. If your server does not respond correctly, the actual request never runs.
Why preflights exist
Simple GET requests (like loading an image from another domain) were already possible before CORS existed — via HTML img tags. For more complex requests that can have side effects (POST, PUT, DELETE), browsers add a safety check: ask the server first before sending data.
What triggers a preflight
You get a preflight when a cross-origin request uses:
- Method: PUT, DELETE, PATCH (POST only triggers preflight if it has custom headers)
- Custom headers: Authorization, Content-Type: application/json, or any non-standard header
- Credentials: any request with credentials: 'include'
What the browser sends
OPTIONS /api/data HTTP/1.1 Host: api.example.com Origin: https://app.example.com Access-Control-Request-Method: POST Access-Control-Request-Headers: Authorization, Content-Type
The browser is asking: "Is a POST from https://app.example.com with Authorization and Content-Type headers allowed?"
What your server must respond
HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://app.example.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Authorization, Content-Type Access-Control-Allow-Credentials: true Access-Control-Max-Age: 86400
The status must be 200 or 204. If your server returns 404 or 405 for OPTIONS, the browser blocks the actual request and shows a CORS error.
Postman does not send preflights
Postman is a developer tool — it does not enforce browser security policies. If your API works in Postman but fails in the browser, a missing preflight handler is almost always the reason. This is why "CORS errors only in browser" is such a common complaint.
Caching preflights
Access-Control-Max-Age: 86400 tells the browser to cache the preflight result for 24 hours. Without it, the browser sends OPTIONS before every single cross-origin request with custom headers.