What CORS Errors Actually Mean (And How to Fix Them)

By Sarfaraz Khan ·

If you've ever seen Access to fetch at '...' from origin '...' has been blocked by CORS policy in the console and immediately reached for a workaround involving mode: "no-cors", this post is for you — that "fix" doesn't actually fix anything, it just hides the response from your own code.

The request usually already worked

The single most confusing thing about CORS is this: the request almost always succeeds. Open your Network tab and you'll usually see a 200 sitting right there, response body and all. CORS isn't a network failure — it's the browser deciding, after the fact, whether your JavaScript is allowed to read a response that already arrived. The request left, the server answered, and then the browser stepped in between the response and your fetch() call and said no.

That's why curl or Postman "work fine" for the exact same request that fails in the browser: neither of them enforces CORS at all. It's a browser-only security boundary, full stop. If you want to see the request itself the way the browser sees it — headers, method, whether it's a preflight — a cURL → Code Converter is a fast way to turn what you copied from DevTools into something you can actually inspect line by line.

What the browser is checking

For a "simple" request (GET, or POST with a plain content type), the browser sends the request, gets the response, and then checks one header: Access-Control-Allow-Origin. If it's missing, or set to a different origin than the page making the request, the browser throws the response away before your code ever sees it.

For anything else — a custom header, a PUT/DELETE, Content-Type: application/json on some older server configs — the browser first sends a preflight: an OPTIONS request asking "if I sent the real request, would you allow it?" Your server has to answer that OPTIONS request correctly (status 204 or 200, the right Access-Control-Allow-* headers) before the real request is even attempted. A shockingly common bug is a server that handles the real request fine but has no route for OPTIONS at all, so the preflight itself 404s or 405s and the real request never goes out.

This is exactly the kind of thing that's hard to see just by staring at a failed fetch — you need to know whether you're looking at a failed preflight or a failed real request, and what header was actually missing. That's what a CORS Debugger is for: it fires the actual request, shows you whether a preflight happened, and tells you specifically which Access-Control-Allow-* header would need to change for the browser to accept the response.

The actual fixes, in order of how often they're the real problem

  1. The server isn't sending Access-Control-Allow-Origin at all. Add it, matching the calling origin exactly (or * if the endpoint is genuinely public and doesn't use credentials).
  2. You're sending cookies (credentials: "include") but the server sends Access-Control-Allow-Origin: *. Wildcard origins are explicitly incompatible with credentialed requests — the server has to echo back the specific origin, and also send Access-Control-Allow-Credentials: true.
  3. A custom header (like Authorization or an API key header) isn't in Access-Control-Allow-Headers on the preflight response. The browser checks this list literally; a header your server accepts on the real request but forgot to list on the OPTIONS response still gets blocked.
  4. Something in front of your API — a CDN, a load balancer, an API gateway — is stripping the headers your app code correctly sends. This one's sneaky because your server logs show the right headers going out, but they never reach the browser. If you've confirmed the headers are correct at the app layer and it's still failing, check every hop between your server and the internet, not just your own code. An HTTP Status Code & Header Reference is useful here for double-checking exactly which header name and casing the spec expects, since a typo'd header name fails silently — the browser doesn't warn you "close, but not quite."

None of this requires disabling CORS checks in your browser for development, and please don't get in the habit of doing that — it trains you to "fix" the symptom instead of the one header that was actually missing.

Tools mentioned in this post