You wire up a frontend to call your API, hit refresh, and the console lights up red: "Access to fetch … has been blocked by CORS policy." Your API works fine in a direct test, so what gives?
The frustrating truth: CORS is working as intended. It's a browser security feature, not a malfunction — and once you understand it, the fix is straightforward.
What CORS is
CORS (Cross-Origin Resource Sharing) is a browser rule about which sites can call which APIs. By default, browsers enforce the same-origin policy: a page at https://myapp.com can't make JavaScript requests to https://api.other.com unless that other server explicitly allows it.
The key insight: CORS is enforced by the browser, to protect users — not by your server, and not against you. Your API is fine; the browser is refusing to hand the response to JavaScript from a different origin unless the server says it's allowed.
That's why the same request works from a tool like curl (no browser, no CORS) but fails in the browser.
What counts as a different "origin"
An origin is the combination of scheme + host + port. Any difference makes it cross-origin:
| From | To | Cross-origin? |
|---|---|---|
https://app.com | https://api.app.com | ✅ Yes (different host) |
https://app.com | http://app.com | ✅ Yes (different scheme) |
https://app.com:443 | https://app.com:8080 | ✅ Yes (different port) |
https://app.com/a | https://app.com/b | ❌ No (same origin) |
How to fix it (the right way)
The fix lives on the server being called — it must send headers permitting the calling origin:
- Send
Access-Control-Allow-Origin. Set it to the specific origin that's allowed (e.g.https://myapp.com), or*for a fully public API. - Handle the preflight. For many requests the browser first sends an
OPTIONS"preflight" asking permission. Your server must answer it with the allowed methods and headers (Access-Control-Allow-Methods,Access-Control-Allow-Headers). - Allow credentials if needed. Sending cookies/auth? Set
Access-Control-Allow-Credentials: true— and note you then can't use*for the origin; you must name it.
| Header | Purpose |
|---|---|
Access-Control-Allow-Origin | Which origin(s) may read the response |
Access-Control-Allow-Methods | Which HTTP methods are allowed |
Access-Control-Allow-Headers | Which custom headers are allowed |
Access-Control-Allow-Credentials | Whether cookies/auth may be sent |
What not to do
- Don't blindly set
Access-Control-Allow-Origin: *on a private/authenticated API. That opens it to every site. Allow only the origins you trust. - Don't try to "fix" it in the frontend. CORS is server-side policy; a browser can't be told to ignore it (and shouldn't be).
- Don't reach for a proxy as a first resort when the real fix is one header.
The bottom line
| In one line | |
|---|---|
| What | A browser rule on cross-origin requests — a security feature. |
| Why blocked | The target server didn't say your origin is allowed. |
| Fix | Send the right Access-Control-Allow-* headers from the server. |
| Careful | Don't use * on private APIs; name trusted origins. |
CORS errors feel like the browser fighting you, but it's protecting your users. The fix is almost always a small set of response headers on the API — set them correctly (and narrowly), and the red console messages disappear.
If you build APIs, monitor their headers and responses with the API monitoring guide; inspect any URL's headers with the free HTTP header checker.