Logo
Back to Blog
Web Development August 8, 2026 8 min read

Every CORS Header Explained: A Complete Reference

O

OmniWebKit Team

Web Standards

Share:
Article Cover Image

You need to know whether a header belongs on the preflight or the real response, and every answer you find covers three of the seven. Here is all of them in one place, with the rules that are easy to get wrong.

The Complete CORS Headers List

Seven response headers, and three request headers the browser sends for you.

HeaderSent onPurpose
Access-Control-Allow-OriginBothWhich origin may read the response
Access-Control-Allow-CredentialsBothWhether cookies and auth are allowed
Access-Control-Allow-MethodsPreflightWhich HTTP methods are permitted
Access-Control-Allow-HeadersPreflightWhich request headers are permitted
Access-Control-Max-AgePreflightHow long to cache the approval
Access-Control-Expose-HeadersReal responseWhich response headers JavaScript may read
Vary: OriginBothStops caches mixing up per-origin responses

That "sent on" column resolves most confusion. Putting Allow-Methods only on your GET handler achieves nothing, because the browser reads it from the preflight.

To see which of these your endpoint returns, run it through our CORS tester.

Access-Control-Allow-Origin

One origin, or a star. Never a list.

Access-Control-Allow-Origin: https://app.example.com

The value must match the requesting origin exactly. Scheme, host and port all count, and the comparison is case-sensitive.

Multiple origins are handled in your code, not in the header: check the incoming Origin against an allowlist and echo the match. Use exact equality, never a substring check.

A star works for public data but cannot be combined with credentials. We cover why in the wildcard and credentials conflict.

Access-Control-Allow-Headers and the Safelist

Every non-safelisted request header must be named here or the preflight fails.

Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-Id

Header names are case-insensitive, so the casing you use does not matter. What matters is that nothing is missing.

Safelisted headers — Accept, Accept-Language, Content-Language, and Content-Type within its allowed values — are always permitted and need no listing.

Authorization is not safelisted, which is why adding a bearer token to a previously working request suddenly introduces a preflight.

Access-Control-Expose-Headers

The one people discover last, usually while debugging pagination.

By default your JavaScript can read only seven response headers, no matter what the server sent: Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified and Pragma.

Anything else is invisible. Your custom X-Total-Count arrives, sits in the response, and returns null when you ask for it.

Access-Control-Expose-Headers: X-Total-Count, X-Rate-Limit-Remaining

Rate-limit headers are the other common casualty. Clients cannot back off politely if they cannot see how much quota is left.

Access-Control-Max-Age and Its Hidden Ceiling

Caches the preflight approval, but browsers overrule your number.

Access-Control-Max-Age: 86400

Chrome caps this at two hours and Firefox at twenty-four, whatever you send. Setting it to a year is silently reduced, not honoured.

The cache is keyed by URL, method and headers together. A different endpoint re-asks, so this never becomes one approval for a whole API.

Worth setting anyway. On a chatty single-page app it removes a lot of round trips for very little effort.

The Wildcards That Fail Without Telling You

A star works in three headers, but only when credentials are off.

Allow-Headers, Allow-Methods and Expose-Headers all accept * in modern browsers. Enable credentials and the star stops being a wildcard — it is read as a literal header named star, which matches nothing.

No warning appears. Your request simply fails as though the header were absent, and the config looks correct on inspection.

In our experience this is the most expensive silent failure in the whole specification. We list values explicitly on any endpoint that uses credentials, and recommend the same to clients.

Wrapping Up

Learning the CORS headers list is mostly learning which ones belong on the preflight and which on the real response. Get that mapping right and most errors explain themselves.

Send them on error responses too. That single habit prevents the most misleading failure mode, where a 500 masquerades as a CORS problem.

Need the concepts rather than the reference? Start with what CORS is, then how preflight requests work. To inspect a live response, use our HTTP headers checker.

Frequently Asked Questions

Which headers go on the preflight and which on the real response?

+
Allow-Methods, Allow-Headers and Max-Age only matter on the preflight. Allow-Origin and Allow-Credentials must appear on both. Expose-Headers only matters on the real response.

Can Access-Control-Allow-Origin list several origins?

+
No, it takes exactly one value or a single star. Multiple origins have to be handled in your code: match the incoming Origin against an allowlist, then echo the one that matched.

Why can my JavaScript not read a custom response header?

+
Because only seven response headers are exposed by default, and yours is not one of them. Name it in Access-Control-Expose-Headers and it becomes readable. Pagination and rate-limit headers are the usual casualties.

Which response headers are readable without doing anything?

+
Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified and Pragma. Everything else needs to be exposed explicitly, including anything custom you added.

Does Access-Control-Allow-Headers need to list safelisted headers?

+
No, the safelisted ones are always permitted. Listing them anyway is harmless and arguably clearer, but only non-safelisted headers actually require it.

Is header matching case-sensitive?

+
Header names are case-insensitive, so Content-Type and content-type are equivalent. Origin values are not — https://Example.com and https://example.com will not match in a strict comparison.

What does Access-Control-Allow-Private-Network do?

+
It covers requests from a public site to a device on a private network, such as a router or a local dev server. Chrome added it to stop websites quietly probing your LAN, and it needs its own opt-in header.

Why does Timing-Allow-Origin appear in CORS discussions?

+
It is not a CORS header, but it solves a related problem. It controls whether the Resource Timing API exposes detailed load timings cross-origin, which matters when you are measuring third-party performance.

Do these headers need to be on every response?

+
Every cross-origin response, yes — including error responses. Adding them only on the success path is why a 500 so often shows up as a CORS error instead of the real failure.

Does sending unnecessary CORS headers cost anything?

+
Only a few bytes, but there is a subtler cost. An over-permissive list is a security decision made by accident, so we prefer to list exactly what an app uses and revisit it when something breaks.

Tags

#CORS#HTTP#Reference#Headers