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 on the requested resource.
I've personally lost hours thinking my API was broken — only to realize the server was fine, but the browser simply refused to show the response.
If you're stuck in that loop right now, this guide will save you time.
What CORS actually is
CORS is not a server problem — it's a browser restriction.
The browser checks whether your frontend is allowed to access a backend from another origin. If the server doesn't explicitly allow it via response headers, the browser blocks the response from your code.
Important: the request still reaches the server. You just can't read the response in JavaScript.
The 5 most common CORS errors
Error: No 'Access-Control-Allow-Origin' header is present
Cause: Your server doesn't include the required header in its response.
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "http://localhost:3000");
next();
});
Error: Response to preflight request doesn't pass access control check
Cause: The browser sends an OPTIONS request first to check permissions — and your server doesn't handle it.
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);
});
Error: 'Access-Control-Allow-Origin' must not be '*' when credentials are included
Cause: Cookies and Authorization headers require a specific origin — wildcards don't work with credentials.
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "http://localhost:3000");
res.header("Access-Control-Allow-Credentials", "true");
next();
});
Error: Method PUT is not allowed by Access-Control-Allow-Methods
res.header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
Error: Request header field Authorization is not allowed
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
Fix by stack
Express / Node.js
import cors from "cors";
app.use(cors({
origin: "http://localhost:3000",
credentials: true
}));
nginx
location /api/ {
add_header 'Access-Control-Allow-Origin' 'http://localhost:3000';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Length' 0;
return 204;
}
}
FastAPI
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Apache (.htaccess)
Header set Access-Control-Allow-Origin "http://localhost:3000"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header set Access-Control-Allow-Headers "Content-Type, Authorization"
How preflight actually works
When you send a request with a custom header (like Authorization) or a non-simple method (like PUT), the browser doesn't send your request directly. It first sends an OPTIONS request to ask: "Is this allowed?"
Browser → OPTIONS /api/data → Server
← 200 + CORS headers ←
Browser → PUT /api/data → Server
← response ←
If the server doesn't respond to OPTIONS correctly — or takes too long — the actual request never gets sent. That's why fixing preflight handling is often the first step.
The credentials trap
When you use credentials: 'include' in fetch (for cookies or session auth), the wildcard * stops working entirely. You must specify the exact origin AND set Access-Control-Allow-Credentials: true.
// This will NOT work with credentials
res.header("Access-Control-Allow-Origin", "*");
// This will
res.header("Access-Control-Allow-Origin", "https://yourfrontend.com");
res.header("Access-Control-Allow-Credentials", "true");
Quick debug checklist
- [ ] Server returns
Access-Control-Allow-Originwith the correct origin - [ ] OPTIONS requests return 200/204 with CORS headers
- [ ]
Access-Control-Allow-Methodsincludes the method you're using - [ ]
Access-Control-Allow-Headersincludes custom headers you send - [ ] If using credentials — no wildcard
*, specific origin only
Test before you change more code
Before editing middleware, proxy configs, or .htaccess files, verify what headers your server is actually returning.
Paste your endpoint URL into ToolDock CORS Tester — it shows the exact headers for both the main request and the preflight, so you know exactly what's missing before touching your code.
Top comments (0)