CORS Headers Builder
Build a full set of CORS headers for an API: Access-Control-Allow-Origin, Methods, Headers, Credentials and Max-Age.
CORS in depth: origins, preflight, credentials and the headers that actually matter
CORS (Cross-Origin Resource Sharing) is the mechanism that lets a page on origin A make HTTP requests to an API on origin B. It is part of the WHATWG Fetch standard, descended from the W3C CORS recommendation, and enforced entirely by the browser β the server only suggests what it allows, the browser decides whether to expose the response to JavaScript.
An origin is the tuple scheme + host + port β https://example.com:443 is one origin, http://example.com is another, and https://api.example.com is a third. By default the same-origin policy blocks scripts from reading responses across origins; CORS is the controlled opt-in that lets servers say "yes, that origin can read this".
Simple requests vs preflight
A simple request uses GET, HEAD or POST, only standard headers, and a Content-Type of application/x-www-form-urlencoded, multipart/form-data or text/plain. The browser just sends the request with an Origin: https://app.com header and the server replies with Access-Control-Allow-Origin: https://app.com (or * for public APIs).
Anything else β PUT, DELETE, PATCH, custom headers like Authorization or X-API-Key, or a JSON Content-Type β triggers a preflight. The browser sends an OPTIONS request first and the server must answer with the full set of allow headers:
Access-Control-Allow-Origin: https://app.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Max-Age tells the browser how long to cache the preflight (in seconds) β without it, every state-changing request fires two round-trips. Chrome caps at 7200, Firefox at 86400.
Credentials: cookies and Authorization
When the client sends cookies, HTTP auth or a fetch(..., { credentials: 'include' }) request, the server must reply with Access-Control-Allow-Credentials: true. Critically, the Origin header in the response can NEVER be * when credentials are allowed β it must be the literal origin that called you. Most frameworks implement this as "reflect the request's Origin if it is in the allow-list".
To let your JS read non-standard response headers (pagination, rate-limit counters), expose them: Access-Control-Expose-Headers: X-Total-Count, X-Pagination, X-RateLimit-Remaining. Without this header the browser hides them even though they reach the network layer.
Configuring CORS in real servers
- Express:
app.use(cors({ origin: ['https://app.com'], credentials: true, maxAge: 86400 }))β thecorsnpm package handles OPTIONS automatically. - nginx:
add_header Access-Control-Allow-Origin "$http_origin" always;plus a dedicatedlocationblock that returns 204 for OPTIONS. - AWS CloudFront: attach a Response Headers Policy to the behaviour; CloudFront forwards
Originonly if you whitelist it in the cache key. - AWS API Gateway: enable CORS per-resource β it generates a mock OPTIONS integration.
- S3 static hosting: configure the bucket-level
CORSConfigurationXML (separate from bucket policy).
Common pitfalls
Allow-Origin: *combined withAllow-Credentials: trueβ the browser silently blocks the response. Reflect the specific origin instead.- The CORS spec does not allow multiple values in
Allow-Originβ you must echo a single origin from your allow-list (the reflect-origin pattern). - CDN cache without
Vary: Origincan serve the wrong CORS response to a different origin β always add the Vary header. - Preflight failures are easy to miss β the OPTIONS request returns 200 but the browser still blocks the real request because
Allow-Methodswas incomplete. - Redirects do not preserve the
Originheader on preflight in older browsers β avoid 301/302 on API routes.
FAQ
Why do I see "CORS error" in the console? The server did not return an Access-Control-Allow-Origin header matching your page's origin, or did not respond 2xx to the preflight. Inspect the Network tab β the OPTIONS request will show exactly which header is missing.
Can I bypass CORS from the client side? No. CORS is enforced by the browser; only the server can authorise the read. Workarounds (CORS proxies, browser flags) only work in development. In production, fix the server or move the call server-side.
Does preflight happen on every request? Only on non-simple requests, and only if the cache is cold. Set Access-Control-Max-Age to several hours and the browser reuses the result for that origin/method/header combination.
Is CORS a security feature? It is a relaxation feature, not a hardening one. It allows cross-origin reads that the same-origin policy would block. It does not protect a server from CSRF, XSS, or direct curl/Postman calls β those don't go through a browser.
How do I support multiple origins? Maintain an allow-list, check the incoming Origin against it, and reflect that exact value back. Always add Vary: Origin so caches segment responses correctly.
Related Tools
Handwriting Generator
Convert typed text into an image with handwriting appearance. Useful for adding a personal touch to digital work.
Resume Generator
Fill a simple printable A4 CV from a form with personal data, education and experience.
Favicon Generator
Generate a favicon from text/emoji in all common sizes (16, 32, 48, 64, 192, 512). PNG download.