What is CORS and Why is My JavaScript fetch Blocked?

Hero image for What is CORS and Why is My JavaScript fetch Blocked? Image by Jason Leung.
Hero image for 'What is CORS and Why is My JavaScript fetch Blocked?' Image by Jason Leung.

CORS is one of those frontend problems that makes beginners feel like the browser is being deliberately rude. The request looks valid. The endpoint exists. The server may even respond when you test it elsewhere. But the browser still blocks the call and fills the console with warnings about crossorigin requests.

That is frustrating mainly because it looks like a network problem when it is really a browser security rule.

Once you understand that, CORS stops feeling random. It is still annoying, but at least it becomes predictable.


CORS Sits on Top of the Same‑Origin Policy

To understand CORS, you need the sameorigin policy first.

Browsers do not let any page read data from any other origin by default. That would be a security disaster. If it were allowed, a malicious website could quietly make requests on your behalf to other sites and read back the results too easily.

An origin is defined by the combination of:

  • protocol
  • hostname
  • port

So these count as different origins:

  • https://example.com
  • http://example.com
  • https://api.example.com
  • https://example.com:8080

If your frontend code on one origin tries to read a response from another origin, the browser checks whether that crossorigin read is allowed.

CORS is the mechanism for that permission.


What CORS Actually Means

CORS stands for CrossOrigin Resource Sharing.

In practical terms, it is a system of HTTP headers that let a server say, "yes, this browser page from that other origin is allowed to read my response".

If the server does not grant permission in the way the browser expects, the browser blocks access to the response from JavaScript.

That last part matters. A CORS problem is not just "the request failed on the wire". Quite often the request really did go out and the server really did respond. The browser is simply refusing to expose the response to your frontend code because the crossorigin rules were not satisfied.


A Basic Example

Imagine a page running on:

https://shop.example.com

and your frontend code tries to fetch data from:

https://api.example.net/products
const loadProducts = async (): Promise<void> => {  const response = await fetch('https://api.example.net/products');  const products = await response.json();  console.log(products);};

That is a crossorigin request because the hostname differs. The browser will only let your code read the response if the API replies with the right CORS headers.


The Server Has to Opt in

This is the part many beginners need stated very plainly:

frontend JavaScript does not get to decide CORS by itself.

The server you are calling must return headers that permit the crossorigin request. A common example is:

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

or sometimes:

Access-Control-Allow-Origin: *

No surprise, then, that CORS often feels annoying from the frontend side. You can trigger the request, but you cannot simply override the browser's security model from your JavaScript.


Why It Sometimes Works in Postman or Curl

This confuses a lot of people.

You test the endpoint in Postman or cURL and it works perfectly, so you assume the browser must be wrong. Usually it is not.

The key point is that CORS is a browser security feature. Tools like Postman and cURL are not browsers, so they do not enforce the sameorigin policy in the same way.

That means an endpoint can respond successfully in Postman and still fail from frontend code because the browser is applying rules those other tools do not care about.

If you remember nothing else about CORS, remember this:

working in Postman does not prove that browser JavaScript will be allowed to read the response.


Some Requests Trigger a Preflight Check

Not every crossorigin request is treated the same way.

Some simple requests can be sent directly and then checked against the response headers. Others trigger a preflight request first. This is usually an OPTIONS request the browser sends to ask the server which methods, headers, or credentials are allowed.

You are more likely to hit a preflight when you:

  • use methods such as PUT, PATCH, or DELETE
  • send custom headers
  • send content types outside the simpler allowed set

That explains why a request can look fine until one extra header turns it into a CORS problem. The browser is no longer only checking the final response. It may also be checking whether the server handled the preflight correctly.


What Front‑End Developers Can Fix

This is where being precise helps.

Frontend developers usually can:

  • call the correct endpoint
  • avoid unnecessary custom headers
  • avoid sending credentials unless genuinely needed
  • work with a sameorigin back end or proxy
  • give the backend team the exact browser error and request details

Frontend developers usually cannot:

  • add AccessControlAllowOrigin from browser code
  • force the browser to ignore the sameorigin policy
  • fix a server that refuses the required crossorigin headers

In reality, many CORS problems are integration issues between the front end and back end rather than purely frontend bugs.


Credentials Make the Rules Stricter

If you include cookies or authentication information in a crossorigin request, the rules become tighter.

For example:

await fetch('https://api.example.net/account', {  credentials: 'include',});

In cases like that, wildcard CORS settings are often not enough. The server has to be more explicit about which origin is allowed, and it has to allow credentials properly as well.

Beginners often get stuck here because they add credentials to a request without realising they have changed the browser's expectations about the response headers.


The Quickest Way to Debug a CORS Issue

When I hit a CORS problem, I usually check these things first:

  1. What is the exact origin of the page making the request?
  2. What is the exact origin of the API endpoint?
  3. Is the browser error about the final response or a preflight request?
  4. Are custom headers or credentials involved?
  5. Does the server return the expected AccessControlAllow* headers?

That is usually enough to separate "frontend request shape problem" from "server permissions problem".

It is also worth checking the browser network panel instead of only reading the console. The network view often shows whether the preflight failed, which headers were sent, and what the server returned.


CORS is Not the Same Thing as Authentication

This is another beginner mixup worth avoiding.

CORS does not decide whether a user is authorised to access data. It decides whether a browser page from one origin is allowed to read a response from another origin.

You can have:

  • a request that passes authentication but fails CORS
  • a request that passes CORS but fails authentication

They are different layers of the problem.


Wrapping up

CORS is the browser's way of enforcing crossorigin read permissions. It exists because browsers should not let any website read responses from any other website by default. Once you understand that the server must opt in with the right headers, the problem becomes much easier to reason about. Frustrating, yes. Random, no.

Key Takeaways

  • CORS is a browser security mechanism built on top of the sameorigin policy.
  • Crossorigin requests are allowed only when the server returns the right headers.
  • Frontend JavaScript cannot simply switch CORS off from the browser.
  • Postman and cURL can succeed even when browser code is blocked, because they are not enforcing browser CORS rules.
  • Custom headers, nonsimple methods, and credentials often make CORS stricter by triggering preflight checks.

Once you stop treating CORS as a vague fetch failure and start treating it as an explicit browser permission check, the errors become much easier to diagnose.


Categories:

  1. Development
  2. Front‑End Development
  3. Guides
  4. JavaScript
  5. Security