Stop guessing and start understanding. Here’s the real reason your cross-origin fetch requests are being blocked (and how to fix them the right way).

Introduction
You run this in your frontend:
fetch("https://api.example.com/data");
…and boom 💥
Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000'
has been blocked by CORS policy.
You try adding random headers.
You install Chrome extensions.
You even shout at your backend.
But the browser doesn’t care.
CORS short for Cross-Origin Resource Sharing is one of the most misunderstood parts of web development.
This post will explain what CORS really is, how it works step by step, and why your requests fail (even when your code looks fine).
1️. The Core Idea Behind CORS
Let’s start with why CORS exists at all.
By default, browsers follow a Same-Origin Policy (SOP), which means a web page can only make requests to the same domain, port, and protocol it was loaded from.
So:
- ✅
https://myapp.com
→https://myapp.com/api
→ allowed - ❌
https://myapp.com
→https://api.example.com
→ blocked
This protects users from malicious scripts stealing data from other sites.
CORS is the browser’s permission system for breaking that rule safely.
It’s a negotiation between the browser and the server. The server must say,
“Hey, browser, it’s okay, I trust this origin.”
2️. The Two Types of CORS Requests
Not all cross-origin requests are the same.
CORS has two major categories:
1. Simple Requests
These are “safe” requests with:
- Method:
GET
,POST
, orHEAD
- Headers: Only basic ones like
Accept
,Content-Type
, etc. - Content-Type:
application/x-www-form-urlencoded
,multipart/form-data
, ortext/plain
The browser directly sends them, but includes an Origin
header.
Example:
Origin: http://localhost:3000
If the response from the server contains:
Access-Control-Allow-Origin: http://localhost:3000
✅ The browser allows it.
❌ If not, it blocks it even if the server responded successfully.
2. Preflighted Requests
If your request includes custom headers or non-standard methods (likePUT
, DELETE
, or PATCH
The browser first sends an OPTIONS request for a “preflight” check.
Example:
OPTIONS /api/data
Origin: http://localhost:3000
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
Your server must respond:
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Only then will the browser send the actual PUT /api/data
request.
If the preflight fails → the real request never happens.
💡 You never see this OPTIONS call in your code; it’s handled entirely by the browser.
3️. The Most Common CORS Failure Scenarios
Let’s go through the top ones you’ve probably hit 👇
Case 1: Missing Access-Control-Allow-Origin
Your API responds fine in Postman but fails in the browser.
That’s because Postman ignores CORS browsers that enforce it.
Fix: Add this header from your backend:
Access-Control-Allow-Origin: http://localhost:3000
Or for testing only (⚠️ not in production):
Access-Control-Allow-Origin: *
Case 2: Wrong Allowed Methods
Your API only allows GET
, but you send a PUT
.
Server must include:
Access-Control-Allow-Methods: GET, POST, PUT
Otherwise, the browser blocks the preflight response.
Case 3: Missing Allowed Headers
If you send custom headers like Authorization
You must whitelist them:
Access-Control-Allow-Headers: Content-Type, Authorization
Otherwise, the browser silently drops the request before your server even receives it.
Case 4: Missing Credentials Support
When sending cookies or auth tokens, you must enable credentials explicitly:
Frontend:
fetch("https://api.example.com/data", {
credentials: "include",
});
Backend:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost:3000
⚠️ You cannot use *
(wildcard) With credentials, browsers will reject it for security reasons.
❌ Case 5: Misconfigured Proxy Setup
In dev environments (like React or Next.js), you often proxy /api
to your backend. If that proxy doesn’t forward the Origin
header correctly, CORS fails even if your backend is configured.
✅ Always make sure your dev proxy (like Vite or Webpack) includes changeOrigin: true
or is similar.
4️ Visual: How a CORS Request Actually Works
[ Browser (http://localhost:3000) ]
|
| OPTIONS /api/data
| -------------------->
| Origin: http://localhost:3000
|
| <--------------------
| Access-Control-Allow-Origin: http://localhost:3000
| Access-Control-Allow-Methods: GET, POST, PUT
|
| PUT /api/data
| -------------------->
|
| <--------------------
| 200 OK ✅
The preflight (OPTIONS) check ensures safety; the main request runs only if the server grants permission.
5️. Debugging CORS Like a Pro
✅ Step 1: Check the browser console for the full error message.
It usually tells you exactly which header or method is missing.
✅ Step 2: Inspect the Network tab → OPTIONS request.
Look for missing or mismatched headers.
✅ Step 3: Confirm your backend is sending correct CORS headers not just for GET
, but for all methods.
✅ Step 4: Don’t use Chrome extensions or client-side hacks; they disable CORS for you, not your users.
6️. Real Fix Examples (Backend)
Node.js / Express
import express from "express";
import cors from "cors";
const app = express();
app.use(cors({ origin: "http://localhost:3000", credentials: true }));
NestJS
app.enableCors({
origin: "http://localhost:3000",
credentials: true,
});
Django
CORS_ALLOWED_ORIGINS = ["http://localhost:3000"]
CORS_ALLOW_CREDENTIALS = True
✅ Always fix it on the backend, never in the browser.
7️. Why Postman Works (and Browser Doesn’t)
Because Postman isn’t bound by the Same-Origin Policy, it sends requests directly to the server.
Browsers enforce CORS as a client-side security layer to protect users, not servers.
So when something works in Postman but fails in Chrome, it’s almost always a missing server header.
8️. Quick Recap: The Right CORS Setup

Why CORS Is a Good Thing
CORS errors aren’t bugs; they’re browser safety checks.
Without it, any website could send hidden requests using your credentials to another domain, like a malicious banking site.
CORS exists to protect users, not developers.
Once you understand it, you stop fighting it and start configuring it right.
Conclusion
CORS isn’t a “frontend problem.”
It’s a contract between your browser and your server.
✅ Remember:
- Browser sends
Origin
- Server responds with matching
Access-Control-*
headers - The browser decides if it’s allowed
Get that handshake right, and your “CORS policy” errors disappear forever.
Stop guessing. Start configuring.
Call to Action
Still struggling with a CORS issue in your project?
Drop your setup (frontend + backend) in the comments, I’ll help you spot what’s missing 👇
And if your teammate still installs “CORS Unblock” extensions…
Share this post, they’ll finally understand how CORS actually works.
Leave a Reply