How to Fix CORS Errors — Every Common Cause + Fix (2026)
The real reasons "Access-Control-Allow-Origin" errors happen and how to fix each one — preflight requests, credentials, wildcard origins, cache issues, and dev vs prod.
What CORS actually is
CORS (Cross-Origin Resource Sharing) is a browser security feature. Servers advertise WHICH origins are allowed to read their responses via the Access-Control-Allow-Origin header. Browsers refuse to expose the response to JavaScript when the origin doesn't match. The server always ANSWERS the request — CORS just controls whether the browser reveals that answer to your code.
Key implication: CORS errors are NEVER security holes. They are the browser telling you: "the server said something but didn't authorize you to read it."
The most common CORS errors — and their fix
1. "No 'Access-Control-Allow-Origin' header is present on the requested resource"
Cause: Server didn't send the header at all.
Fix: Add on the server:
Access-Control-Allow-Origin: https://your-app.com
Or for public APIs:
Access-Control-Allow-Origin: *
Wildcards do NOT work if you also need credentials. See error 3 below.
2. "The 'Access-Control-Allow-Origin' header has a value 'https://foo.com' that is not equal to the supplied origin"
Cause: The server hardcoded ONE origin but you're requesting from a different one.
Fix: Read the browser's Origin header on the server side, check it against your allowlist, and echo it back:
// Express example
const allowed = ['https://app.com', 'https://staging.app.com', 'http://localhost:3000'];
if (allowed.includes(req.headers.origin)) {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Vary', 'Origin'); // critical for caching correctness
}
The Vary: Origin header tells caches (Cloudflare, browser cache) that the response depends on the origin — otherwise a request from origin A can get cached and served for origin B.
3. "The value of the 'Access-Control-Allow-Credentials' header is 'true' but the 'Access-Control-Allow-Origin' is '*'"
Cause: You want cookies/auth to be sent, but you're using wildcard origin. Browsers REFUSE this combination.
Fix: Echo the specific origin (as in error 2), not the wildcard. Also set Access-Control-Allow-Credentials: true on both the preflight OPTIONS and the actual response. And on the client:
fetch(url, { credentials: 'include' })
4. "Response to preflight request doesn't pass access control check"
Cause: The browser sent a preflight OPTIONS request but your server didn't respond correctly (returned 404, 405, or didn't send the CORS headers on the preflight).
Fix: Handle OPTIONS explicitly:
// Express: put BEFORE your routes
app.options('*', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
res.sendStatus(204);
});
5. "Method DELETE is not allowed by Access-Control-Allow-Methods"
Cause: Your preflight response listed some methods but not the one you're using.
Fix: Include all methods you use in the header. GET and POST for simple requests don't need to be listed (they're always allowed) but everything else does.
6. "Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers"
Cause: You're sending a custom header the server didn't authorize.
Fix: Add it to Access-Control-Allow-Headers on the preflight response.
Which requests trigger a preflight?
The browser sends a preflight OPTIONS FIRST if the request is not a "simple request." A simple request is:
- Method: GET, HEAD, or POST
- Only "safelisted" headers: Accept, Accept-Language, Content-Language, Content-Type (limited to a few values)
- Content-Type is application/x-www-form-urlencoded, multipart/form-data, or text/plain
Everything else preflights. This means any JSON POST (Content-Type: application/json) preflights. Any request with an Authorization header preflights. Any DELETE, PUT, or PATCH preflights.
Development workarounds (NOT production)
Vite / Webpack dev proxy
Proxy your API calls through the dev server — same origin, no CORS at all:
// vite.config.js
export default {
server: {
proxy: {
'/api': 'http://localhost:4000'
}
}
}
Browser flag (do NOT use for production dev)
Chrome can be launched with --disable-web-security --user-data-dir=/tmp/chrome-nocors. Useful for one-off debugging. Do NOT use this browser instance for real browsing.
CORS proxy (for reading public APIs from a static site)
Route the request through a small proxy you control that adds the CORS headers. Cloudflare Workers is a great host for this — 30 lines of code, free tier covers most static-site use cases. See how we do it on the newspaper directory: a Cloudflare Worker fetches the publisher's RSS and adds Access-Control-Allow-Origin: *.
Production don'ts
- Don't use
Access-Control-Allow-Origin: *on an authenticated API — it means anyone's site can read your API's responses when a user is logged in. - Don't blindly echo the Origin without an allowlist — that's the same as
*with extra steps. - Don't forget
Vary: Origin— caches will serve the wrong CORS headers otherwise. - Don't skip the OPTIONS handler — a lot of CORS "bugs" are just missing preflight handling.
Related
What is CORS? (deeper explainer) · HTTP 403 Forbidden · HTTP 401 Unauthorized.
Featured Tools
Try these free tools directly in your browser — no sign-up required.
Regex Tester
Test and debug regular expressions in real time. Highlights matches, capture groups, and supports JavaScript regex flags for instant pattern validation.
JWT Decoder
Decode and inspect JSON Web Tokens (JWTs) instantly. View header, payload, and signature without a secret key. Debug authentication tokens safely.
Base64 Encoder / Decoder
Encode text or decode Base64 strings instantly online. Convert between plain text and Base64 encoding for data URLs, authentication headers, and API tokens.