CORS Header Generator

Frontend developers staring at "blocked by CORS policy" in the console, backend engineers opening an API to a partner app, and platform teams standardizing edge header config all need the same Access-Control set done correctly the first time. This generator builds Allow-Origin, Allow-Methods, Allow-Headers, Expose-Headers, Allow-Credentials, and Max-Age values, then formats them for nginx, Apache, Netlify, Vercel, or an Express middleware. It also warns about the wildcard-plus-credentials trap and preflight gaps before the config reaches a server.

Target
 

How the Generator Assembles Access-Control Headers

The options object is normalized first: comma-separated method and header lists are split, trimmed, and re-joined so the emitted values never contain stray spaces. buildCorsHeaders then emits the response header set in a fixed order — origin, methods, allowed headers, exposed headers, credentials, max-age, and Vary: Origin whenever the origin is not the wildcard. Each target formatter wraps that same header list in the syntax its platform expects, so switching targets never changes the policy, only the delivery mechanism.

The warning pass runs before rendering. It checks for the wildcard-plus-credentials conflict the browser rejects, multiple origins in one field, default ports embedded in the origin, a missing OPTIONS method, and Max-Age values above the 86400 second practical ceiling. Warnings are advisory; the config still renders so you can experiment, but each message names the exact browser behavior that will bite you in production.

Preflight flow in plain terms

When a request is not "simple", the browser sends OPTIONS first and reads Allow-Methods and Allow-Headers from the response. If the server answers 200 with the wrong headers, or answers 404 because the route only defines POST, the browser cancels the actual request. The generator's preflight status field sets the short-circuit code (204 or 200) for the OPTIONS branch in nginx and Express snippets.

Vary: Origin and shared caches

When Allow-Origin reflects a request origin instead of returning *, the response must include Vary: Origin. Without it a CDN can cache app-a's headers and serve them to app-b, producing intermittent failures that look like random CORS bugs. The generator adds Vary: Origin automatically for non-wildcard origins and omits it for * because the wildcard value does not vary per request.

Before and after: opening an API to one frontend

Before (preflight fails)
location /api/ {
    add_header Access-Control-Allow-Origin *;
    proxy_pass http://backend;
}
After (credentialed + preflight)
location /api/ {
    if ($request_method = OPTIONS) {
        return 204;
    }
    add_header Access-Control-Allow-Origin https://app.example.com always;
    add_header Access-Control-Allow-Methods 'GET, POST, PUT, PATCH, DELETE, OPTIONS' always;
    add_header Access-Control-Allow-Headers 'Content-Type, Authorization, X-Requested-With' always;
    add_header Access-Control-Allow-Credentials true always;
    add_header Access-Control-Max-Age 86400 always;
    add_header Vary Origin always;
    proxy_pass http://backend;
}

Use Cases

Developer Production Workflow
Unblock a PUT or DELETE call failing preflight in devtools Expose custom response headers to a partner browser app Move identical CORS policy across nginx and Netlify deploys
Add Authorization header support without opening every origin Keep credentialed cookie flows off the wildcard origin Review a vendor's requested headers before granting access
Debug intermittent CORS failures caused by a missing Vary Tune Max-Age after a header change ships to production Document the Express middleware policy next to the API spec

Common Mistakes and Troubleshooting

The classic mistake is two Access-Control-Allow-Origin headers on one response, usually because a framework plugin and a reverse proxy both add one. Browsers treat duplicates as a failure even if one of them is correct. The second is echoing the Origin header back without Vary: Origin, which breaks under CDN caching. The third is allowing the Authorization header in the preflight but forgetting that the actual request must also send it.

When the browser console shows "credential is not supported if Access-Control-Allow-Origin is '*'", switch the origin field to the exact frontend URL and keep credentials enabled. When only some endpoints fail, compare the OPTIONS response with the GET response for the same path; a route-level middleware that skips OPTIONS is a frequent cause.

Best Practices for CORS Configuration

List explicit origins instead of * once credentials or private data are involved. Keep the method list to what the API actually implements. Allow-Headers should name only the headers clients send today; adding * there is rejected in modern browsers when credentials are on. Set Max-Age to 3600 during development and 86400 after the header set is stable. Always send Vary: Origin when the response depends on the request origin.

Pair this generator with the HTTP header parser to inspect responses from a live API, the content security policy generator and CSP validator for the adjacent browser security layer, the security headers auditor for a full response-header audit, and the cache control header generator so caching rules do not undo the CORS work.

CORS Header Generator FAQ

What is CORS and why do browsers enforce it?

Cross-Origin Resource Sharing is a set of HTTP response headers that tell a browser which foreign origins may read a response. Browsers block cross-origin fetches by default to protect cookies and credentials; the Access-Control-Allow-Origin header opts specific origins in. Without it, JavaScript on https://app.example.com cannot read a response from https://api.example.com even though the request may reach the server.

What is the difference between simple and preflighted requests?

Simple requests are GET, HEAD, or POST with only safe headers, and the browser sends them directly. Anything else — PUT, DELETE, custom headers, non-standard content types — triggers a preflight OPTIONS request first. The preflight asks which methods and headers the server allows; if the response is missing or wrong, the real request is never sent and the console shows a CORS error.

Why can I not combine Allow-Origin: * with credentials?

The CORS specification forbids the wildcard origin when Access-Control-Allow-Credentials is true, because any site could then read credentialed responses. Browsers reject the combination outright. List the exact calling origin instead, set credentials to true, and add Vary: Origin so caches do not serve the header of one origin to another.

Why does my CORS config work in curl but fail in the browser?

curl ignores CORS headers because CORS is enforced only by browsers. A response can reach curl fine and still be unreadable from JavaScript if the Allow-Origin header is missing, wrong, or sent twice. Compare the Origin request header the browser sends with the Allow-Origin response value; they must match exactly including scheme and port.

Should Access-Control-Max-Age be very large?

Preflight results are cached per origin, method, and header set up to the Max-Age value in seconds. Values above 86400 are ignored by some browsers, and a long cache makes it slow to notice a policy change. Start at 3600 while iterating and raise it to 86400 once the API headers are stable.

Where do these headers go for a static host or CDN?

Netlify uses a _headers file where each path maps to header name-value pairs. Vercel reads a headers array in vercel.json. nginx sends them with add_header inside the matching server or location block. Apache needs mod_headers and a Header always set directive, typically in .htaccess or the virtual host. The generator emits each format so you can paste into the file your platform expects.

Does this tool upload my API origins?

No. Header strings are assembled in the browser and never sent over the network. Internal origin lists, custom header names, and max-age values stay in the tab; close it and nothing is retained.