CORS error: “blocked by CORS policy,” explained and fixed

The full console message — “Access to fetch at '…' from origin '…' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present” — means a web page's JavaScript asked an API for data, and the API's server didn't include the permission slip browsers require. The request usually reached the server fine. The browser is refusing to hand over the response.

What CORS actually is

Browsers enforce the same-origin policy: a page from one origin (scheme + host + port) can't freely read responses from another. CORS is the controlled exception: the server adds headers like Access-Control-Allow-Origin declaring which origins may read its responses. No header, no read — by design, to stop malicious sites from quietly calling your bank's API with your login. Two consequences follow: the fix lives on the API server, and tools like curl/Postman don't show the error because they aren't browsers and don't enforce the policy.

The fix — on the API server

  1. Add the allow-origin header to API responses. For a public API: Access-Control-Allow-Origin: *. For an API used by one app, name that origin exactly: Access-Control-Allow-Origin: https://app.example.com — required (not *) when requests carry credentials/cookies.
  2. Handle the preflight (OPTIONS). Requests with custom headers or JSON content types trigger a preflight OPTIONS request first. The server must answer it with Access-Control-Allow-Methods and Access-Control-Allow-Headers covering what the app sends (commonly Authorization and Content-Type). “Simple GET works, POST fails” almost always means preflight isn't handled.
  3. Use the framework's CORS middleware rather than hand-rolling: Express (cors package), Django (django-cors-headers), Laravel (built-in HandleCors), Spring (@CrossOrigin / WebMvcConfigurer), ASP.NET (AddCors). Hand-set headers get overwritten or duplicated by frameworks and proxies in ways that produce stale errors.
  4. Check for duplicates and proxies. Two different Access-Control-Allow-Origin values on one response is invalid and browsers reject it — a CDN or reverse proxy adding the header on top of the app's is a frequent cause.
  5. Remember origins are exact: scheme, host, and port all count. http://localhost:3000, http://127.0.0.1:3000, and https://localhost:3000 are three different origins.
Browser extensions that “disable CORS” are for local debugging only — they change nothing for real users and weaken the browser's security while active. And a CORS error is never fixed from the calling page alone: if the API isn't yours, the frontend workaround is calling it through your own backend (server-to-server requests have no CORS), not a public proxy.

If it still happens

Open the browser's Network tab, click the failed request, and read both the request's Origin header and the response's Access-Control headers verbatim — then compare against what the app intended. Nine out of ten stubborn CORS bugs are a mismatch visible in that one panel: a trailing slash, the wrong scheme, a missing allowed header, or a proxy rewriting the response.

Common questions

Why does it work in Postman but not the browser? Postman isn't a browser and doesn't enforce the same-origin policy. The API is fine; the browser is asking for the permission headers the API didn't send.

Is a CORS error a security problem? It's a security feature working as intended. It becomes a problem only when it's misconfigured — either blocking legitimate apps, or (rarely) an API allowing * together with credentials, which browsers reject for safety.

Related errors

502 Bad Gateway · TypeError: cannot read properties · EADDRINUSE