If you’ve done any frontend development, you’ve seen this error in your console:
Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
And if you’re like most developers, your first instinct was to Google “disable CORS” and paste whatever Stack Overflow answer appeared first. I’ve been there. Let me explain what’s actually happening and how to fix it properly — because the quick fixes usually create security holes.
What CORS Actually Is (30-Second Version)
CORS (Cross-Origin Resource Sharing) is a browser security mechanism. When your frontend at http://localhost:3000 makes a request to https://api.example.com, the browser checks whether api.example.com has explicitly said “yes, I accept requests from localhost:3000.”
If the server doesn’t include the right headers in its response, the browser blocks your JavaScript from reading the response. The request still reaches the server — CORS is enforced by the browser, not the server.
This is important: CORS is not a server-side security feature. It’s a browser-side restriction. Your API is still receiving and processing the request. The browser just won’t let your JavaScript see the response.
The 5 Most Common CORS Errors (and Fixes)
Error #1: No Access-Control-Allow-Origin Header
The error:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Why it happens: Your server isn’t sending the CORS header at all.
The fix depends on your backend:
Express.js:
const cors = require('cors');
// Allow specific origin (recommended)
app.use(cors({
origin: 'http://localhost:3000'
}));
// Or allow multiple origins
app.use(cors({
origin: ['http://localhost:3000', 'https://myapp.com']
}));
Django:
# settings.py
INSTALLED_APPS = [
'corsheaders',
# ...
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # Must be high in the list
# ...
]
CORS_ALLOWED_ORIGINS = [
'http://localhost:3000',
'https://myapp.com',
]
Nginx:
location /api/ {
add_header 'Access-Control-Allow-Origin' 'https://myapp.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
}
The mistake to avoid: Don’t use Access-Control-Allow-Origin: * with credentials. I’ll explain why below.
Error #2: Preflight Request Failure
The error:
Response to preflight request doesn't pass access control check
Why it happens: Before making certain requests (POST with JSON, requests with custom headers, PUT/DELETE methods), the browser sends an OPTIONS request first — a “preflight.” If your server doesn’t handle OPTIONS requests properly, the actual request never fires.
What triggers a preflight:
Content-Typeis anything other thanapplication/x-www-form-urlencoded,multipart/form-data, ortext/plain- Custom headers like
Authorization,X-Custom-Header - HTTP methods other than GET, HEAD, or POST
The fix: Make sure your server responds to OPTIONS requests with the correct headers:
// Express.js — the cors middleware handles this automatically
app.use(cors({
origin: 'http://localhost:3000',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
// If you're handling it manually:
app.options('*', (req, res) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.sendStatus(204);
});
I once spent two hours debugging why a PUT request was failing. GET and POST worked fine. The issue? The server returned 405 Method Not Allowed for the preflight OPTIONS request because the router didn’t have an OPTIONS handler for that route.
Error #3: Credentials and Wildcard Origin
The error:
The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*'
when the request's credentials mode is 'include'.
Why it happens: You’re sending cookies or authentication headers, but the server responds with Access-Control-Allow-Origin: *. The browser blocks this combination because a wildcard origin with credentials would allow any website to make authenticated requests to your API.
The fix: Specify the exact origin instead of *, and set Access-Control-Allow-Credentials: true:
// Express.js
app.use(cors({
origin: 'http://localhost:3000', // NOT '*'
credentials: true,
}));
// Frontend fetch call
fetch('https://api.example.com/data', {
credentials: 'include', // Send cookies
});
Error #4: Missing Headers in Access-Control-Allow-Headers
The error:
Request header field 'X-Custom-Header' is not allowed by
Access-Control-Allow-Headers in preflight response.
Why it happens: Your frontend sends a custom header, but the server’s Access-Control-Allow-Headers doesn’t include it.
The fix: Add every custom header your frontend uses:
app.use(cors({
origin: 'http://localhost:3000',
allowedHeaders: [
'Content-Type',
'Authorization',
'X-Custom-Header',
'X-Request-ID',
],
}));
Pro tip: If you’re building an API and keep running into this, you can temporarily log the preflight request to see exactly what headers the browser is asking for:
app.options('*', (req, res, next) => {
console.log('Preflight headers requested:',
req.headers['access-control-request-headers']);
next();
});
Error #5: CORS Works in Development, Breaks in Production
This one is painful because everything works on localhost, and then the deployed version fails.
Common causes:
- Your production URL doesn’t match the allowed origins:
// You allowed http but production uses https
origin: 'http://myapp.com' // ← should be https://myapp.com
-
Your reverse proxy strips CORS headers: Nginx, Cloudflare, or your CDN might strip or override the
Access-Control-Allow-Originheader. Check your infrastructure layer by layer. -
Mixed content: Your frontend is served over HTTPS but makes requests to an HTTP API. Browsers block this regardless of CORS.
-
www vs non-www:
// These are different origins!
'https://myapp.com'
'https://www.myapp.com'
Debugging strategy: Use the browser’s Network tab. Look at the actual response headers of the failing request. If Access-Control-Allow-Origin is missing or wrong, the problem is on the server. If the header is correct but the request still fails, look at the preflight OPTIONS response.
The “Quick Fixes” You Should Avoid
Don’t: Install a CORS-disabling Browser Extension
These extensions strip CORS from all responses, which means any malicious website can access your authenticated sessions on every site. Fine for quick local testing, but never forget to disable it.
Don’t: Proxy Everything Through Your Backend
Some developers route all API calls through their own server to avoid CORS:
Frontend → Your Server → Third-party API
This adds latency, increases server load, and makes your server a single point of failure. Only do this if you need to hide API keys from the frontend.
Don’t: Use Access-Control-Allow-Origin: * in Production
The wildcard is fine for truly public APIs (like a public weather API). But if your API has any authentication, user data, or private endpoints, use specific origins. A wildcard combined with a misconfigured endpoint can expose user data.
When CORS Isn’t the Real Problem
Sometimes what looks like a CORS error is actually something else:
- Network error — the server is down or unreachable, but the browser reports it as a CORS failure
- SSL certificate issue — the API’s certificate is invalid, and the browser can’t complete the request
- Ad blocker or browser extension — blocking the request entirely
If you’ve verified your CORS headers are correct and it still doesn’t work, check the Network tab for the actual HTTP status code. A 0 status usually means the request never completed.
Debugging Checklist
When you hit a CORS error, go through this in order:
- Open the Network tab — find the failing request
- Check for a preflight — is there an OPTIONS request before your actual request?
- Inspect the response headers — is
Access-Control-Allow-Originpresent and correct? - Check the origin exactly — protocol, domain, and port must all match
- Check credentials — if you’re sending cookies, make sure the origin isn’t
* - Test with curl —
curl -I https://api.example.com/datato verify the server sends the right headers (curl doesn’t enforce CORS)
Working with API responses? Our JSON Formatter can help you inspect and debug the response data once you’ve got CORS sorted out. And if you’re URL-encoding parameters in your API calls, the URL Encoder handles the tricky edge cases.
Final Thought
CORS errors feel like the browser is fighting you, but it’s actually protecting your users. Once you understand the mechanism — browser asks server, server says yes or no, browser enforces — the fix becomes obvious every time. Configure your server correctly, match your origins precisely, and you’ll stop seeing that red error message for good.
For more on web security fundamentals, check out XSS Prevention with HTML Entity Encoding and our Encoding & Hashing Guide.