Skip to content

CORS Explained

How the browser decides between simple and preflighted cross-origin requests, and the fetch and response headers that control each exchange.

The browser blocks script from reading a cross-origin response unless the server opts in. These rows cover the request metadata, the response headers, and the rules that split simple requests from preflighted ones.

Reference table · 28 entries
28 of 28 rows
Simple requests
Method setOnly GET, HEAD, and POST can travel as a simple request.GET /api/items
Safelisted headersOnly these headers may be set by script without a preflight.Accept, Accept-Language, Content-Language
Content-Type valuesThree body types keep the request simple; JSON is not one of them.application/x-www-form-urlencoded
Upload listenersA listener on XMLHttpRequest.upload progress strips the simple status.xhr.upload.addEventListener('progress', fn)
Streamed bodyA ReadableStream body forces the browser to preflight the request.new Request(url, { body: stream })
OriginThe browser names the origin that started the request; page code cannot change it.Origin: https://app.example.com
Response gateScript reads the body only when the answer carries a matching Allow-Origin.Access-Control-Allow-Origin: https://app.example.com
Preflighted requests
Method triggerAny method beyond GET, HEAD, and POST sends the preflight first.PUT, PATCH, DELETE
Header triggerAny non-safelisted request header sends the preflight first.Authorization, X-API-Key
Content-Type triggerJSON and XML bodies send the preflight first.Content-Type: application/json
OPTIONS probeThe browser sends the preflight on its own; page code never sends it.OPTIONS /api/items
Access-Control-Request-MethodTells the server which method the real request will use.DELETE
Access-Control-Request-HeadersLists every non-safelisted header the real request will carry.Content-Type, X-CSRF-Token
Preflight cacheThe browser reuses one approval for Max-Age seconds instead of re-probing.Access-Control-Max-Age: 600
Response headers
Access-Control-Allow-OriginNames the one origin that may read the response, or the wildcard for all.https://app.example.com
Access-Control-Allow-MethodsLists the methods the real request may use.GET, POST, DELETE
Access-Control-Allow-HeadersApproves the headers the preflight asked for.Content-Type, Authorization
Access-Control-Allow-CredentialsLets cookies and HTTP auth cross origins with the response.true
Access-Control-Expose-HeadersLists response headers the page may read from script.X-Request-Id, Link
Access-Control-Max-AgeCaps how long the browser caches the preflight result.600
Vary: OriginMarks a dynamic Origin echo so caches keep one copy per origin.Vary: Origin
Common errors
Wildcard with credentialsThe wildcard never combines with credentialed requests; the browser blocks the response.Allow-Origin: * + credentials: 'include'
Comma list of originsAllow-Origin takes exactly one origin; a list is invalid and the request fails.https://a.com, https://b.com (invalid)
Missing Allow-OriginScript sees a network error with status 0; the response arrived but stays unreadable.TypeError: Failed to fetch
Origin: nullSandboxed iframes and file: pages send null; treat it as untrusted input.Origin: null
Redirect after preflightA cross-origin redirect between preflight and real request voids the approval.302 Location: https://other.example.com
Allow-Headers gapOne unapproved header fails the whole request, even when the others match.requested X-Trace, allowed Content-Type
Credentials defaultfetch sends cookies same-origin only; cross-origin cookies need an explicit include.fetch(url, { credentials: 'include' })