Fix "No Access-Control-Allow-Origin Header" Error
You made a fetch request and the browser threw it back with a red console line about CORS. The fix depends entirely on which string the browser printed. Most articles explain one cause; this page maps every common console message to the exact header you need to add on the server. The browser enforces the policy, but the fix is almost always server-side: the response must carry the right Access-Control-* headers, and the preflight (when there is one) must succeed first.
First, decode the exact error string
CORS is governed by the WHATWG Fetch Standard, and browsers report failures with very specific wording. Read your console line and jump to the matching branch below.
"No 'Access-Control-Allow-Origin' header is present on the requested resource"
The server responded, but it did not send an Access-Control-Allow-Origin header at all. The browser hid the response because nothing granted your origin permission. The fix is to add that header to the actual response from the server:
Access-Control-Allow-Origin: https://yourapp.com
You can reflect a single trusted origin, or use * to allow any origin. Critical caveat: * is forbidden when the request sends credentials (cookies, HTTP auth, or fetch with credentials: "include"). In that case you must echo back the specific origin and add Access-Control-Allow-Credentials: true. A wildcard plus credentials always fails per the spec.
"Request header field Authorization is not allowed by Access-Control-Allow-Headers"
Sending an Authorization header (or any custom header like X-API-Key) makes the request "non-simple," so the browser sends a preflight OPTIONS request first. The preflight asks, via Access-Control-Request-Headers, whether your header is permitted. The server must answer with that header listed in Access-Control-Allow-Headers:
Access-Control-Allow-Headers: Authorization, Content-Type, X-API-Key
If the message says a header is "missing token in Access-Control-Allow-Headers," it means the same thing: the server's allow-list does not include the header your request is trying to send. Add the missing token to the list.
Why application/json triggers a preflight
A request with Content-Type: application/json is not a "simple request." Per the Fetch Standard, only application/x-www-form-urlencoded, multipart/form-data, and text/plain qualify as simple content types. Anything else, including JSON, forces a preflight OPTIONS call. That is why a GET works but your JSON POST suddenly fails. To satisfy the preflight you must allow the method and the content-type header:
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type
"Response to preflight request doesn't pass access control check" / non-2xx status
The preflight OPTIONS request must return a successful status, typically 204 No Content or 200 OK. If your framework returns 401, 403, 404, or redirects the OPTIONS call, the browser treats the preflight as failed before the real request is ever sent. Two gotchas: your auth middleware must not challenge the credential-less OPTIONS request, and your router must actually have a route for OPTIONS on that path.
The case-sensitivity and value gotchas
HTTP header names are case-insensitive, so Access-Control-Allow-Origin and access-control-allow-origin are equivalent. But the header values are matched strictly. The origin you send must match exactly: scheme, host, and port. https://yourapp.com does not match http://yourapp.com, and http://localhost:3000 does not match http://localhost:5173. No trailing slash on the origin value. Likewise, method and header names in the allow-lists are matched against what the browser requested; a typo or omitted entry fails the check. To inspect exactly what the server is returning, run the response through the HTTP header inspector and compare it character-for-character against what the request asked for.
Copy-paste fixes by framework
Each snippet sets the four headers that cover the cases above. Replace the origin with your real front-end origin in production rather than shipping *.
Express (Node.js)
const cors = require('cors');
app.use(cors({
origin: 'https://yourapp.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
The cors middleware automatically answers the preflight OPTIONS with a 204, which avoids the non-2xx trap.
Flask (Python)
from flask_cors import CORS
CORS(app,
origins=['https://yourapp.com'],
methods=['GET', 'POST', 'PUT', 'DELETE'],
allow_headers=['Content-Type', 'Authorization'],
supports_credentials=True)
Spring (Java)
@CrossOrigin(
origins = "https://yourapp.com",
methods = { RequestMethod.GET, RequestMethod.POST,
RequestMethod.PUT, RequestMethod.DELETE },
allowedHeaders = { "Content-Type", "Authorization" },
allowCredentials = "true")
Nginx
add_header Access-Control-Allow-Origin "https://yourapp.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://yourapp.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
return 204;
}
The always flag matters: without it, Nginx omits the headers on error responses, so a 4xx from your backend would leave the browser with no CORS headers and the original error returns.
Generate and verify your headers
Rather than hand-editing four headers across config files, paste your origin, methods, and allowed headers into the CORS headers generator to produce ready-made config blocks for Express, Nginx, Apache, and raw HTTP. Once deployed, confirm the live response actually carries them: fetch the endpoint and run the response through the HTTP header inspector to read the Access-Control-* values and the OPTIONS status code, then compare them field by field against what the request asked for.
If your error has a different shape (a redirect mid-preflight, a credentials mismatch, or a header that only appears on some routes), the step-by-step walkthrough in how to debug CORS errors covers the diagnostic order: confirm the response status, then the origin match, then the method, then the header allow-list.
The one-minute checklist
- Header missing entirely: add
Access-Control-Allow-Originwith your exact origin or*. - Credentials in play: never use
*; echo the specific origin and addAccess-Control-Allow-Credentials: true. - Custom or Authorization header rejected: list it in
Access-Control-Allow-Headers. - JSON body: expect a preflight; allow the method and
Content-Type. - Preflight fails: make
OPTIONSreturn 2xx and skip auth on it. - Still failing: check scheme, host, and port match exactly, with no trailing slash.
Frequently Asked Questions
For public APIs with no cookies or credentials, yes, * works. But if the request sends credentials (cookies, HTTP auth, or fetch with credentials: include), the wildcard is forbidden by the Fetch Standard. You must echo back the specific requesting origin and also add Access-Control-Allow-Credentials: true.
A Content-Type of application/json makes the request non-simple, so the browser sends a preflight OPTIONS request first. Only x-www-form-urlencoded, multipart/form-data, and text/plain are simple content types. Your server must allow the method and the Content-Type header in its preflight response.
A successful 2xx status, typically 204 No Content or 200 OK. If your auth middleware returns 401 or 403 on the OPTIONS request, or the route redirects it, the browser treats the preflight as failed and never sends the real request. Exclude OPTIONS from authentication checks.
The header name is case-insensitive like all HTTP header names, so the casing of Access-Control-Allow-Origin does not matter. The value is matched strictly, though: the origin must match scheme, host, and port exactly, with no trailing slash. https and http, or different ports, will not match.
Nginx drops add_header directives on error responses unless you append the always flag. If your backend returns a 4xx or 5xx, the CORS headers vanish and the browser reports the original error. Add always to every Access-Control header, and handle the OPTIONS method with an explicit 204 return.