What it means: The API answered, but its headers did not allow your page's origin to read the response.
Free online tool — CORS checker: instant results, no signup.
Sends a cross-origin request and an OPTIONS preflight to your URL and shows the Access-Control-* headers it returns.
Chrome and Edge print one Console line per blocked request, built in Blink's cors_error_string.cc:
Access to fetch at 'https://api.example.com/v1/items' from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.«fetch» becomes «XMLHttpRequest» or «font» depending on what made the request, and the text after CORS policy: is the actual reason. Firefox words the same check as Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ... (Reason: CORS header 'Access-Control-Allow-Origin' missing). Status code: 200. (security.properties).
CORS is enforced by the browser, not the server. The request usually reached the API and got an answer; the browser then refused to hand that answer to a script from another origin (scheme, host and port must all match to count as the same origin). The script only receives TypeError: Failed to fetch. That is also why the same call works in curl or Postman: they do not enforce CORS at all.
| Text after "CORS policy:" | Cause | Fix on the API |
|---|---|---|
| No 'Access-Control-Allow-Origin' header is present on the requested resource. | The header is missing, often only on error responses or on a path behind another proxy | Send it on every response, including 4xx/5xx (nginx: add_header ... always) |
| The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin. | A hard-coded origin, or a cached response for another origin | Echo an allow-listed Origin; add Vary: Origin |
| The 'Access-Control-Allow-Origin' header contains multiple values '...', but only one is allowed. | Both the app and the proxy add the header | Set it in one layer only |
| The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. | credentials: 'include' or withCredentials with * | Return the exact origin and Access-Control-Allow-Credentials: true |
| Response to preflight request doesn't pass access control check: It does not have HTTP ok status. | OPTIONS gets 401, 404, 405 or a redirect target | Answer OPTIONS with 200 or 204 before auth |
| Redirect is not allowed for a preflight request. | http to https or trailing-slash redirect on OPTIONS | Call the final URL directly |
| Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response. | Header missing from the allow list | List it in Access-Control-Allow-Headers |
| Method PUT is not allowed by Access-Control-Allow-Methods in preflight response. | Method missing from the allow list | Add it to Access-Control-Allow-Methods |
A preflight is an automatic OPTIONS request the browser sends before a cross-origin call that uses a method other than GET, HEAD or POST, a Content-Type such as application/json, or custom headers like Authorization. Note that Access-Control-Allow-Headers: * does not cover Authorization: Firefox already warns that it must be listed explicitly. More on preflights: CORS preflight.
Most real-world cases are a layer problem rather than a missing line of code. The application sets the headers, but nginx answers OPTIONS itself with 405, an authentication layer returns 401 to the preflight because browsers never send credentials on it, or an error page from the proxy (502, 504, 413) goes out without any CORS headers, so a backend failure surfaces as a CORS error. In nginx, add_header only applies to 2xx and 3xx responses unless you add always:
location /v1/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE' always;
add_header Access-Control-Allow-Headers 'Content-Type, Authorization' always;
add_header Access-Control-Max-Age 600 always;
return 204;
}
add_header Access-Control-Allow-Origin $http_origin always;
add_header Vary Origin always;
proxy_pass http://app;
}Echoing $http_origin as shown allows every origin; in production, map it against an allow list first. A CDN that caches the response must vary on Origin, or one site's allowed origin is served to another.
curl -sS -D - -o /dev/null https://api.example.com/v1/items \
-H 'Origin: https://app.example.com'curl -sS -D - -o /dev/null -X OPTIONS https://api.example.com/v1/items \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: content-type,authorization'Expect a 2xx status, your origin in access-control-allow-origin, and the method and headers in the allow lists.Origin header and check that the allow header is still there.*), add Access-Control-Allow-Credentials: true, and set credentials: 'include' on the client.mode: 'no-cors' does not fix it: it returns an opaque response your script cannot read (MDN).Background on the header model: CORS explained and how to configure CORS.
Access-Control-Allow-Origin is a response header; setting it on the request does nothing except trigger a preflight.http://localhost:3000 but deploying from https://app.example.com. The origin is compared exactly, including scheme and port.https://app.example.com/). An origin has no path.* on an API that relies on cookies. Browsers reject that combination, and it is unsafe on private APIs.curl -sS -D - -o /dev/null -X OPTIONS https://api.example.com/v1/items \
-H 'Origin: https://app.example.com' -H 'Access-Control-Request-Method: POST' \
| grep -i '^access-control'
# access-control-allow-origin: https://app.example.com
# access-control-allow-methods: GET, POST, PUT, DELETEThen reload the page: the Network tab should show the OPTIONS request with a 2xx status followed by the real request, and the Console should stay clean. The CORS checker sends the simple request and the preflight for any URL and grades the answer.
No. Only the server that answers the request can allow your origin. From the front-end you can only route the call through a backend you control.
Postman does not enforce CORS. The browser does, so a missing or wrong Access-Control-Allow-Origin header only breaks the browser call.
Only for public data that needs no cookies or auth. Browsers refuse * with credentials, and private APIs should return an allow-listed origin instead.
The error response was generated without CORS headers, often by a proxy. Add the headers to error responses too, for example with nginx add_header ... always.
It removes the error but returns an opaque response with no readable body or headers. It is only useful for fire-and-forget requests.
Free plan — 10 monitors, checks every 5 min, no card required. Upgrade for 1-minute interval and multi-region monitoring.