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