This is the page I keep coming back to when I need to remember the exact syntax or behavior of a CORS header. I’m putting it all in one place so you don’t have to hunt through MDN and Stack Overflow.
Response Headers (What Your Server Sends)
These are the headers your API server needs to send. The browser reads these to decide whether to allow the cross-origin request.
Access-Control-Allow-Origin
The single most important CORS header. Without it, nothing works.
Access-Control-Allow-Origin: https://myapp.com
Three valid values:
- A specific origin like
https://myapp.com— only that origin can access the API *— any origin can access (not compatible with credentials)- Nothing — the request is blocked
You cannot list multiple origins in a single header. If you need to allow multiple origins, your server needs to check the request’s Origin header and return the matching one:
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
const requestOrigin = req.headers.origin;
if (allowedOrigins.includes(requestOrigin)) {
res.setHeader('Access-Control-Allow-Origin', requestOrigin);
}
```text
### Access-Control-Allow-Methods
Which HTTP methods the actual request is allowed to use. Only used in preflight responses.
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
List every method your API accepts. Don't use `*` here if you can help it — be explicit.
### Access-Control-Allow-Headers
Which request headers the actual request is allowed to include. Only used in preflight responses.
Access-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header
Common headers to include:
- `Content-Type` — required if sending JSON
- `Authorization` — required for bearer tokens
- `X-Requested-With` — used by some frameworks (jQuery, Axios)
- `Accept` — rarely required but sometimes
### Access-Control-Allow-Credentials
Whether the response can include credentials (cookies, authorization headers, TLS certificates).
Access-Control-Allow-Credentials: true
Important rules:
- Must be `true` or `false` (it's not a list)
- Cannot be `true` when `Allow-Origin` is `*`
- The request must also be sent with `credentials: 'include'` (fetch) or `withCredentials: true` (XMLHttpRequest)
- When using credentials, cookies are sent with every request, even preflight
### Access-Control-Expose-Headers
By default, the browser only exposes a few "CORS-safelisted" response headers to JavaScript: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma. Any other headers are visible in the network tab but not accessible via `response.headers`.
Access-Control-Expose-Headers: X-Total-Count, X-Page-Number, X-Request-Id
If your API returns custom headers that your frontend needs to read (like pagination info), you MUST list them here.
This catches a lot of people off guard. They can see the header in DevTools but `response.headers.get('X-Total-Count')` returns null. That's because the browser is hiding it from JavaScript. Add it to Expose-Headers and it works.
### Access-Control-Max-Age
How long the preflight result can be cached, in seconds.
Access-Control-Max-Age: 86400
Without this, the browser sends a preflight OPTIONS request before every single API call. With `Max-Age: 86400`, it caches the result for 24 hours.
Set this to a reasonable value. 86400 (24 hours) is a good default. Some people use longer (604800 = 7 days) if their CORS config rarely changes.
### Access-Control-Allow-Private-Network
Chrome's Private Network Access feature requires this header when a public website accesses a private network resource (localhost, 192.168.x.x, etc.).
Access-Control-Allow-Private-Network: true
## Request Headers (What the Browser Sends Automatically)
You don't set these manually. The browser adds them. But understanding them helps with debugging.
### Origin
Sent with every cross-origin request (and some same-origin requests).
Origin: https://myapp.com
This tells the server where the request is coming from. The server uses this to decide what to put in `Access-Control-Allow-Origin`.
### Access-Control-Request-Method
Sent only in preflight (OPTIONS) requests. Tells the server what method the actual request will use.
Access-Control-Request-Method: PUT
### Access-Control-Request-Headers
Sent only in preflight requests. Tells the server what custom headers the actual request will include.
Access-Control-Request-Headers: Content-Type, Authorization
## Complete Real-World Example
Let's say your React app at `https://myapp.com` wants to PUT a user profile update to your API at `https://api.example.com/users/123` with a JSON body and a JWT token.
**Step 1: The browser sends a preflight**
OPTIONS /users/123 HTTP/1.1 Host: api.example.com Origin: https://myapp.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: Content-Type, Authorization
The browser asks: "Can I send a PUT request with JSON content and an Authorization header?"
**Step 2: The server responds**
HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://myapp.com Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Allow-Credentials: true Access-Control-Max-Age: 86400
The server says: "Yes, you can. Here are the rules."
**Step 3: The browser sends the actual request**
PUT /users/123 HTTP/1.1 Host: api.example.com Origin: https://myapp.com Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiJ9…
{“name”: “Updated Name”, “email”: “[email protected]”}
**Step 4: The server responds**
HTTP/1.1 200 OK Content-Type: application/json Access-Control-Allow-Origin: https://myapp.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: X-Request-Id X-Request-Id: abc-123
{“id”: 123, “name”: “Updated Name”, “email”: “[email protected]”}
The browser sees the correct headers and allows your JavaScript to read the response. Done.
## Verify Your CORS
Use [headertest.com](https://headertest.com) to check your CORS configuration.