DEV Community

Cover image for CORS Errors Explained: A Practical Debug Guide for 2026
Andrew Rozumny
Andrew Rozumny

Posted on

CORS Errors Explained: A Practical Debug Guide for 2026

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();
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

Error: Method PUT is not allowed by Access-Control-Allow-Methods

res.header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
Enter fullscreen mode Exit fullscreen mode

Error: Request header field Authorization is not allowed

res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
Enter fullscreen mode Exit fullscreen mode

Fix by stack

Express / Node.js

import cors from "cors";

app.use(cors({
  origin: "http://localhost:3000",
  credentials: true
}));
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

FastAPI

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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            ←
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

Quick debug checklist

  • [ ] Server returns Access-Control-Allow-Origin with the correct origin
  • [ ] OPTIONS requests return 200/204 with CORS headers
  • [ ] Access-Control-Allow-Methods includes the method you're using
  • [ ] Access-Control-Allow-Headers includes 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)