Guides / Web

CORS: The Browser Knows the API Exists. It Just Won’t Let You Touch It.

Your API works in curl and Postman, but the browser blocks it. CORS is a browser security rule about which origins may read cross-origin responses.

PUBLISHED · UPDATED · 10 MIN

You built the frontend. You built the API. Both are online. Both work individually.

Naturally, they refuse to speak to each other.

The console gives you:

blocked by CORS policy

The server exists. The URL opens. The response may even have arrived. The browser still refuses to give it to your JavaScript.

Meet CORS.

Start with the origin

CORS means Cross-Origin Resource Sharing. An origin is roughly the combination of:

scheme + hostname + port

https://example.com and https://api.example.com are different origins. HTTP and HTTPS are different. Different ports are different too.

To a human they may all feel like “my website.” To the browser they are separate neighborhoods with separate security guards.

Why does the browser care?

Imagine you are logged in to email or an admin dashboard and then visit a malicious page. That page can run JavaScript too. If arbitrary pages could freely read responses from other sites using your existing browser state, private data would be much easier to steal.

CORS is not the entire browser security model, but it is an important part of cross-origin access control.

The key fact is that CORS is enforced by the browser.

“But it works in Postman”

Exactly. Postman and curl are not ordinary web pages operating inside the browser's same-origin security model.

curl https://api.example.com/data

may work while:

fetch("https://api.example.com/data")

fails. The network may be fine. The browser is saying that the server did not clearly grant this page permission to read the response.

The famous header

Access-Control-Allow-Origin: https://app.example.com

This is the server telling the browser that the listed origin may read the response.

Truly public resources sometimes use:

Access-Control-Allow-Origin: *

Convenient, but not appropriate for every authenticated scenario.

Then OPTIONS arrives

You wrote a POST request, but the Network panel suddenly shows OPTIONS first. That is often a preflight request.

The browser is effectively asking: “If I later send this method and these headers from this origin, will you allow it?”

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

If preflight fails, the real POST may never be sent. That is why backend logs can show no POST at all. POST is still outside while OPTIONS argues with security.

mode: "no-cors" is usually not the fix

This often removes the visible error:

fetch(url, { mode: "no-cors" })

but the result is normally an opaque response that JavaScript cannot read the way you wanted. You have mostly told the browser: “Send it, but do not let me see what comes back.”

Useful in narrow cases. Not a magic CORS-off switch.

Credentials make the rules stricter

If you send cookies with credentials: "include", the server commonly needs:

Access-Control-Allow-Credentials: true

and a specific allowed origin rather than a casual wildcard. Authenticated cross-origin requests deserve more care than “everyone on the internet is invited.”

CORS is not authentication

Allowing only your frontend origin does not secure private API data. curl, scripts and other non-browser clients can still call the backend.

Authentication and authorization still belong on the server.

CORS decides whether browser JavaScript may read a cross-origin response. Authentication decides whether the request should receive the data at all. Different jobs.

A practical debugging order

  1. Did the request reach the server?
  2. Was there an OPTIONS request first?
  3. What status did preflight return?
  4. Is Access-Control-Allow-Origin present and exact?
  5. Are the methods allowed?
  6. Are custom headers allowed?
  7. Are credentials involved?
  8. Did a redirect change the request?
  9. Did a CDN, Worker or proxy alter the headers?

The console tells you that something went wrong. The Network panel usually tells you which conversation went wrong.

The final lesson

CORS feels absurd the first time because the API exists, the server responds and the browser can reach it, yet your code cannot read it.

Once you understand origins, preflight and response headers, the mystery shrinks.

The browser has not forgotten networking.

It knows exactly where the API is.

It would simply like written permission before handing the response to this script.