How to Fix CORS Errors
“Blocked by CORS policy” is the browser protecting your users, not a bug in your code. The fix is always on the server that receives the request. This guide explains what the browser checks and gives working configurations for the common cases.
What CORS actually is
Browsers block JavaScript on https://app.example.com from reading responses from another origin (scheme + host + port), such as https://api.example.com, unless that server says it’s allowed. The server says so with Access-Control-Allow-* response headers. Requests from curl, Postman or your backend aren’t affected — that is why “it works in Postman” and fails in the browser.
Read the error message
- “No ‘Access-Control-Allow-Origin’ header is present” — the server didn’t send CORS headers at all, or an error page (404, 500) was returned without them.
- “Response to preflight request doesn’t pass access control check” — the
OPTIONSrequest failed: it wasn’t handled, returned an error, or lacked headers. - “The value of the ‘Access-Control-Allow-Origin’ header must not be the wildcard ‘*’ when credentials mode is ‘include’” — you send cookies, so the server must echo the exact origin.
Look at the failing request in DevTools → Network, or check the real response headers with the HTTP Header Checker.
Simple requests and preflight
A request is “simple” if it is a GET, HEAD or POST with only basic headers and a form or plain-text body. Anything else — Content-Type: application/json, an Authorization header, PUT or DELETE — makes the browser send an OPTIONS preflight first. Your server must answer it with status 204 or 200 and these headers:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600
Working configurations
Express with the cors package:
const cors = require('cors');
app.use(cors({
origin: ['https://app.example.com'],
credentials: true, // only if you send cookies
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
Nginx:
location /api/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Max-Age 600;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
proxy_pass http://backend;
}
The always flag matters: without it Nginx drops the header on error responses, and the browser then reports a CORS error instead of the real 500. Generate headers for other servers (Vercel, Netlify, Cloudflare Workers, Apache) with the CORS Header Generator.
Cookies and credentials
To send cookies cross-origin, the request needs credentials: 'include' (fetch) and the server must respond with Access-Control-Allow-Credentials: true and the exact origin — never *. The cookie itself needs SameSite=None; Secure. When you echo the origin from a list, also send Vary: Origin so caches don’t serve one site’s headers to another.
What not to do
- Don’t reflect any
Originheader back with credentials enabled — that lets every website read your users’ data. - Don’t “fix” CORS with a browser extension or a public proxy; it only hides the problem on your machine.
- If the API is yours and on the same site, a reverse proxy (same origin) removes CORS entirely.
Reproduce the browser’s request with the API Tester, or turn it into code with cURL to Code.