Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.
If you’ve seen this error, you’re not alone. It’s probably the most common frontend error in web development. Here’s what’s actually happening and how to fix it.
What CORS Actually Is
CORS (Cross-Origin Resource Sharing) is a browser security feature. It prevents your frontend JavaScript from making requests to a different domain than the one serving your page.
Same origin (allowed by default):
- Your page:
https://mysite.com - Your API:
https://mysite.com/api/users✅
Cross origin (blocked by default):
- Your page:
http://localhost:3000 - Your API:
https://api.example.com❌
The browser blocks the response, not the request. Your API actually receives and processes the request — the browser just refuses to show you the response.
Fix 1: Add CORS Headers to Your Backend (Proper Fix)
The correct solution is to have your API send the right headers.
Express.js:
const cors = require('cors');
app.use(cors()); // allows all origins
// Or be specific:
app.use(cors({ origin: 'http://localhost:3000' }));
Python (Flask):
from flask_cors import CORS
CORS(app) # allows all origins
Python (FastAPI):
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(CORSMiddleware, allow_origins=["*"])
Nginx:
location /api/ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
}
Fix 2: Use a Proxy in Development
If you don’t control the API, proxy the request through your dev server.
Vite (vite.config.js):
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
Now fetch('/api/users') goes through your dev server, which forwards it to the real API. No CORS issue because the browser only sees your origin.
Fix 3: Use Your Backend as a Proxy
In production, make your own backend call the external API:
// Your backend endpoint
app.get('/api/data', async (req, res) => {
const response = await fetch('https://external-api.com/data');
const data = await response.json();
res.json(data);
});
Your frontend calls your backend (same origin), your backend calls the external API (server-to-server, no CORS).
Fix 4: Check for Preflight Issues
For non-simple requests (POST with JSON, custom headers), the browser sends an OPTIONS preflight request first. Your server must handle it:
// Express
app.options('*', cors()); // handle preflight for all routes
If your API returns 404 or 405 for OPTIONS requests, the preflight fails and you get a CORS error even though your GET/POST would work fine.
Fix 5: Browser Extension (Development Only)
Extensions like “CORS Unblock” disable CORS checking in your browser. Only use this for local development — it doesn’t fix the problem for your users.
Common Mistakes
“I added the header but it still doesn’t work”
- Check if the preflight OPTIONS request is handled
- Check if you’re sending credentials (cookies) — you need
Access-Control-Allow-Credentials: trueand can’t use*for origin
“It works in Postman but not in the browser”
- Postman doesn’t enforce CORS — it’s a browser-only restriction
- This confirms the API works; you just need the right headers
“It works in development but not in production”
- Your dev proxy isn’t running in production
- Update CORS headers to include your production domain
When CORS Is NOT the Problem
Sometimes the error message mentions CORS but the real issue is:
- Network error — the API is down or unreachable
- HTTPS mixed content — your HTTPS page is calling an HTTP API
- DNS issue — the API domain doesn’t resolve