The Exact Error
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.
Or for preflight:
Access to fetch at 'https://api.example.com/users' from origin 'http://localhost:3000'
has been blocked by CORS policy: Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Quick summary: The server did not include
Access-Control-Allow-Originin its response. The browser blocks the response from reaching JavaScript as a security measure. Fix it on the server by adding CORS headers.
Why This Error Happens
1. Server returns no CORS headers ā the API wasn't configured to allow cross-origin requests
2. Preflight fails ā the OPTIONS request handler is missing or returns the wrong headers
3. Wrong origin whitelisted ā Access-Control-Allow-Origin: https://app.example.com but request comes from http://localhost:3000
4. Credentials issue ā using credentials: 'include' but server returns Access-Control-Allow-Origin: * (wildcards and credentials are incompatible)
Step-by-Step Diagnosis
Step 1 ā Confirm it's a CORS error
Open Browser DevTools > Network tab > click the failed request > look for:
- Response headers: Is
Access-Control-Allow-Originmissing? - Request headers: Is
Originpresent? (browser adds it automatically for cross-origin requests)
Step 2 ā Check for preflight
# Does your request send a preflight?
curl -X OPTIONS https://api.example.com/endpoint -H "Origin: http://localhost:3000" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: Content-Type" -v
The OPTIONS response must include:
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Step 3 ā Check credentials
// If you're sending cookies/credentials:
fetch(url, { credentials: 'include' });
// Server CANNOT use wildcard ā must specify exact origin:
// Access-Control-Allow-Origin: http://localhost:3000
// Access-Control-Allow-Credentials: true
Solutions
Solution 1 ā Express: add CORS headers
// Install: npm install cors
import cors from 'cors';
// Allow all origins (development only):
app.use(cors());
// Allow specific origins (production):
app.use(cors({
origin: ['https://app.example.com', 'http://localhost:3000'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // if using cookies
}));
Solution 2 ā Manual headers (any Node.js framework)
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
return res.status(204).end();
}
next();
});
Solution 3 ā Next.js API routes
// app/api/data/route.ts
export async function OPTIONS() {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
Solution 4 ā Development proxy (Vite / CRA)
// vite.config.ts ā proxy to avoid CORS in development
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:4000',
changeOrigin: true,
},
},
},
};
Real-World Examples
AWS API Gateway CORS: Enable CORS in the API Gateway console under each method's Method Response, or use the built-in "Enable CORS" button which sets the required headers.
nginx reverse proxy:
location /api/ {
add_header 'Access-Control-Allow-Origin' $http_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
if ($request_method = OPTIONS) {
add_header 'Access-Control-Max-Age' 1728000;
return 204;
}
proxy_pass http://backend;
}
Quick Reference ā CORS Headers
| Header | Direction | Purpose |
|---|---|---|
Access-Control-Allow-Origin | Response | Which origins are allowed |
Access-Control-Allow-Methods | Response | Which HTTP methods are allowed |
Access-Control-Allow-Headers | Response | Which request headers are allowed |
Access-Control-Allow-Credentials | Response | Allow cookies/credentials |
Access-Control-Max-Age | Response | Cache preflight for N seconds |
Origin | Request | Browser sends automatically |
Prevent This Error in the Future
1. Set CORS headers on every response, including error responses ā a 500 with no CORS headers still triggers a CORS error in the browser.
2. Handle OPTIONS preflight explicitly ā many frameworks don't handle it automatically.
3. Never use Access-Control-Allow-Origin: * with credentials: true ā browsers reject this combination.
Use ToolNinja to Debug Faster
The HTTP Request tool lets you send requests with custom headers and inspect responses ā useful for testing CORS headers outside the browser before debugging in the browser network tab.