Preflight request
/ˈpriːflʌɪt/
A preflight is an OPTIONS request the browser sends before a non-simple cross-origin request, asking the server whether the real request is allowed. The real request only goes out if the preflight response says yes.
When the browser preflights
Any cross-origin request that isn't a "simple" GET/POST/HEAD with a safe Content-Type. In practice you'll see a preflight whenever:
- The method is
PUT,DELETE,PATCH, orCONNECT. - The request carries an
Authorizationheader, a customX-*header, or any header outside the CORS-safelist. - The
Content-Typeis anything other thanapplication/x-www-form-urlencoded,multipart/form-data, ortext/plain— soapplication/jsonalways triggers one.
What it looks like on the wire
// Browser → server
OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, authorization
// Server → browser (says yes, with which origin and methods are allowed)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600
// Then — and only then — the real POST goes:
POST /api/users HTTP/1.1
Host: api.example.com
Authorization: Bearer …
Content-Type: application/json
…
Why it exists
Older HTTP APIs (forms, image embeds, GETs) were always allowed cross-origin — the web was built that way. CORS adds new capabilities (custom headers, non-trivial methods) without breaking that legacy: anything new and potentially destructive requires explicit per-origin opt-in via the preflight.
Why preflights make CORS errors confusing
A preflight failure looks identical to a "regular" CORS failure in the console: "blocked by CORS policy". But the request that failed is the OPTIONS, not your real call. If you only check the failed POST in the Network tab, you'll miss the OPTIONS right before it that's actually the problem.
Practical debugging: in DevTools → Network, filter by "Other" or by status code, find the OPTIONS, and look at its response headers. The missing piece will usually be Access-Control-Allow-Methods or Access-Control-Allow-Headers not including what your real call needs.
Reducing preflight overhead
Preflights add a round-trip per unique (origin, method, headers) combination. The browser caches the answer for Access-Control-Max-Age seconds (max ~7200 in Firefox, ~600 in Chrome). Set a generous Max-Age on the OPTIONS response and you eliminate the cost after the first call.