---
title: "CORS errors: the server answered, the browser said no"
url: https://erroneo.us/daily/cors-error/
description: "A CORS error means the browser refused to hand your script the response. The server often answered fine. Fix it with server headers, not frontend hacks."
type: daily-entry
section: "Developers"
date_published: 2026-10-22
author: erroneo.us Editors
date_modified: 2026-10-22
publisher: erroneo.us
license: All rights reserved. Quote with attribution and a link.
---

# CORS errors: the server answered, the browser said no

> The most misdiagnosed error in web development is not a server crash. It is a browser enforcing a permission slip your server never signed.

*Error of the Day for October 22, 2026 · Developers · by erroneo.us Editors*

- **Wrong:** The API is down: I'm getting a CORS error.
- **Right:** The API answered, but its response lacked an Access-Control-Allow-Origin header that permits my origin, so the browser withheld it.
- **Memory trick:** CORS is the server giving the browser permission. Fix it where the permission is granted: on the server.

## What people get wrong

You call an API from your frontend, the console turns red, and you see something like this:

```
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at https://api.example.com/orders. (Reason: CORS
header 'Access-Control-Allow-Origin' missing).
```

The instinctive reading is "the API is broken." Usually it isn't. In many cases the request reached the server, the server did its work and sent back a perfectly good response. The browser then looked at that response, found no permission to share it with your page, and refused to hand it to your JavaScript.

That is the whole point of CORS. Browsers enforce the [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy): a script loaded from one origin (scheme, host and port) cannot freely read responses from another. [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) is the opt-in mechanism, now defined in the [Fetch Standard](https://fetch.spec.whatwg.org/#http-cors-protocol), by which a server says "yes, that origin may read this."

## Why it happens

Three details make CORS errors feel more mysterious than they are.

**Your code can't see the reason.** [MDN is explicit](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS/Errors): for security reasons, specifics about what went wrong with a CORS request are not available to JavaScript. Your `fetch()` just rejects with a generic network error. The real explanation is only in the browser console, which is why "it works in curl and Postman" is such a common complaint. Those tools aren't browsers and don't enforce the policy at all.

**Preflights.** For requests that could have side effects — custom headers, a JSON `Content-Type`, methods like `PUT` or `DELETE` — the browser first sends an `OPTIONS` [preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) asking permission. If your server or framework doesn't answer `OPTIONS` with the right headers (or answers it with an error status, or with a redirect that some browsers refuse to follow after a preflight), the real request never goes out. Same red message, different cause.

**Credentials change the rules.** If you send cookies or HTTP auth, MDN notes the server must not use the `*` wildcard in `Access-Control-Allow-Origin`; it has to echo an explicit origin, and also send `Access-Control-Allow-Credentials: true`. A config that works anonymously can fail the moment a user logs in.

## The fix

Wrong: Frontend: ~~fetch(url, { mode: "no-cors" })~~ and hope.

Right: Server: respond with Access-Control-Allow-Origin: https://app.example.com (plus allowed methods and headers for preflights).

Why: CORS is a permission the server grants; the client can't grant it to itself.

Most CORS errors can only be resolved on the server, because the server controls whether cross-origin access is allowed. The checklist:

1. Open the browser console and the Network tab. Find the failing request and check whether there was a preflight `OPTIONS` request before it.
2. Confirm the response (and the preflight response) carries `Access-Control-Allow-Origin` matching your page's exact origin, scheme and port included.
3. For preflights, also return `Access-Control-Allow-Methods` and `Access-Control-Allow-Headers` covering what you send.
4. If you send credentials, use an explicit origin, not `*`, plus `Access-Control-Allow-Credentials: true`.
5. Check that error responses (401, 500) include the CORS headers too. Otherwise a real server error shows up disguised as a CORS error.

## The tempting non-fixes

`mode: "no-cors"` silences the error but, per MDN, gives you an opaque response: status 0, empty headers, unreadable body. That's fine for a fire-and-forget beacon and useless for reading data. Browser extensions that disable CORS only "fix" your own machine. If you don't control the API at all, the legitimate route is a small proxy on a server you do control, which fetches the resource and returns it with proper headers.

>

Next time you see "CORS error," translate it in your head to "the browser withheld a response." Then go read the response headers.

## Sources

1. [MDN: Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS)
2. [MDN: CORS errors](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS/Errors)
3. [MDN: Same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)
4. [MDN: Preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request)
5. [WHATWG: Fetch Standard, CORS protocol](https://fetch.spec.whatwg.org/#http-cors-protocol)

## Go deeper

- https://erroneo.us/developers/http-status-codes/
- https://erroneo.us/developers/javascript-errors/
- https://erroneo.us/developers/http-401-vs-403/

---
Source page: https://erroneo.us/daily/cors-error/ · More: https://erroneo.us/llms.txt
