/glossary/cors/preflight-request
What is a CORS preflight request?
- tool
- CORS
- versions
- Fetch Standard
- os
- any
A CORS preflight request is an automatic OPTIONS request that a browser sends to another origin before the real request, when that request is not "simple": a method other than GET, HEAD or POST, a custom header such as Authorization, or a Content-Type like application/json. It carries Origin, Access-Control-Request-Method and Access-Control-Request-Headers; the server must answer with matching Access-Control-Allow-* headers and a 2xx status, or the browser blocks the real request.
What the exchange looks like
A server on port 4103 that answers preflights, and curl sending the same OPTIONS request a browser would send for a PUT with a JSON body:
cat > server.js <<'EOF'
require("http").createServer((req, res) => {
if (req.method === "OPTIONS") {
res.writeHead(204, {
"Access-Control-Allow-Origin": "https://app.example",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "600",
});
return res.end();
}
res.setHeader("Access-Control-Allow-Origin", "https://app.example");
res.end("real response\n");
}).listen(4103, "127.0.0.1");
setTimeout(() => process.exit(0), 5000);
EOF
node server.js &
sleep 1
curl -sS -i -X OPTIONS http://127.0.0.1:4103/items/1 \
-H "Origin: https://app.example" \
-H "Access-Control-Request-Method: PUT" \
-H "Access-Control-Request-Headers: content-type" | grep -v -i '^date:'Output
HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://app.example Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Max-Age: 600 Connection: keep-alive Keep-Alive: timeout=5
The browser compares the answer with the request it wants to make. Here PUT and content-type are allowed for https://app.example, so it proceeds with the real PUT. Access-Control-Max-Age: 600 lets it cache that answer for 10 minutes.
When a preflight is sent
| Request | Preflight? |
|---|---|
| GET or HEAD without custom headers | No |
| POST with Content-Type text/plain, multipart/form-data or application/x-www-form-urlencoded | No |
| POST with Content-Type application/json | Yes |
| PUT, PATCH or DELETE | Yes |
| Any request with Authorization or another custom header | Yes |
| Same-origin request | Never (CORS does not apply) |
Common preflight failures
- The server or a framework route does not handle
OPTIONSand returns404or405. - Authentication middleware rejects the preflight with
401: preflights never carry credentials, soOPTIONSmust be allowed without them. Access-Control-Allow-Headersdoes not list a header the request uses (oftenAuthorization).- With
credentials: "include",Access-Control-Allow-Origin: *is not accepted; the server must echo the exact origin and sendAccess-Control-Allow-Credentials: true.