How to Fix CORS Errors
CORS is a browser security feature, not a server restriction. Your server receives the request fine — the browser blocks the response from reaching your JavaScript. CORS errors never happen with curl, Postman, or server-to-server calls.
The Same-Origin Policy prevents JavaScript on https://app.example.com from reading responses from https://api.other.com unless the API explicitly permits it via CORS headers.
Reading the Error
Chrome: Access to fetch at 'https://api.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.
Common variants:
- "No Access-Control-Allow-Origin header" → server not sending CORS headers at all
- "Value not equal to supplied origin" → wrong origin configured
- "Response to preflight doesn't pass access control check" → OPTIONS request failing
- "Allow-Credentials must be true" → credentials not enabled
Fix 1: Add CORS Headers on Your Server
Express / Node.js
const cors = require('cors');
// Allow specific origin (production)
app.use(cors({
origin: 'https://app.example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400,
}));
// Multiple allowed origins
const allowedOrigins = ['https://app.example.com', 'http://localhost:3000'];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) callback(null, true);
else callback(new Error('Not allowed by CORS'));
},
credentials: true,
}));
Django
# pip install django-cors-headers
INSTALLED_APPS = [..., 'corsheaders']
MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware', ...]
CORS_ALLOWED_ORIGINS = ['https://app.example.com']
CORS_ALLOW_CREDENTIALS = True
FastAPI
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(CORSMiddleware,
allow_origins=['https://app.example.com'],
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)
Nginx
location /api/ {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://app.example.com';
add_header 'Access-Control-Allow-Methods' 'GET,POST,PUT,DELETE,OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type,Authorization';
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
}
Fix 2: Preflight Requests Failing
Complex requests (DELETE, PUT, custom headers like Authorization) trigger an OPTIONS preflight. If OPTIONS returns 404 or missing headers, the real request never happens.
// Express: handle OPTIONS before auth middleware
app.options('*', cors());
app.use(cors({ ... }));
app.use(authMiddleware); // Auth comes AFTER cors
// Skip auth for OPTIONS preflight
function authMiddleware(req, res, next) {
if (req.method === 'OPTIONS') return next();
// verify token...
}
Fix 3: Credentials + Cookies
fetch(url, { credentials: 'include' }); // fetch
axios.get(url, { withCredentials: true }); // axios
Server must use specific origin, not wildcard:
# Wrong: Access-Control-Allow-Origin: * + Access-Control-Allow-Credentials: true
# Right: Access-Control-Allow-Origin: https://app.example.com
# Access-Control-Allow-Credentials: true
Fix 4: APIs You Don't Control
Proxy through your own backend:
app.get('/api/proxy', async (req, res) => {
const data = await fetch('https://third-party-api.com/data');
res.json(await data.json());
});
Common Mistakes
- Duplicate CORS headers — Nginx and Express both adding headers; browser rejects duplicates
- CORS missing on error responses — 401/500 errors may miss the header, use the
alwaysflag - Trailing slash mismatch —
https://app.comandhttps://app.com/are different origins - Not restarting the server — browser may also cache preflight results for
Access-Control-Max-Ageseconds
→ Check HTTP status codes at HTTP Status Codes.