Skip to main content
AllDevToolsHub
2026-07-29
Last reviewed: Aug 2026
NETWORKING
Est Read: 12_MIN

CORS Errors Explained: Every Fix, Every Framework (2026 Guide)

CORS Errors Explained: Every Fix, Every Framework (2026 Guide)
Processing_Node: 01

#1CORS errors explained: how to debug the browser’s cross-origin block

A CORS error means the browser refused to share a cross-origin response because the server did not explicitly allow it.

The fix is usually server-side: send the right headers, handle preflight correctly, and make sure the browser is talking to the origin you intended.


#2What CORS Actually Is (and Why the Browser Enforces It)

The Same-Origin Policy (SOP) is a browser security rule: JavaScript running on https://myapp.com can only read responses from requests made to the same origin, same scheme, same host, same port. Everything else is cross-origin.

CORS, Cross-Origin Resource Sharing, is the mechanism that lets servers selectively relax the Same-Origin Policy. A server adds HTTP headers to its responses that tell the browser: "it is okay to share this response with code from origin X." Without those headers, the browser reads the response, then silently discards it and throws a CORS error into your console.

Keep these three facts in mind before you start debugging:

  1. CORS is enforced by the browser, not the server. curl and Postman do not check CORS, they always get the response. Only browsers do CORS. If your API works in Postman but fails in the browser, CORS is almost certainly why.
  2. The fix is server-side, always. Browser extensions that "disable CORS" are masking the problem in your local browser only. They break for every real user. Never ship code that depends on them.
  3. Preflight is a separate request. For non-simple requests (anything with a custom header, a JSON body, or methods other than GET/POST), the browser sends an OPTIONS request first to ask for permission. Your server must handle this correctly.

#2The Four CORS Error Types, Diagnosed from the Console Message

#3Error 1: "No 'Access-Control-Allow-Origin' header is present"

protocol
Access to fetch at 'https://api.example.com/data' from origin 
'https://myapp.com' has been blocked by CORS policy: No 
'Access-Control-Allow-Origin' header is present on the requested resource.

Cause: The server returned a response with no CORS headers at all. This is the most common error. Your API is running and returning data, but it never told the browser it was okay to share that data.

Fix: Add Access-Control-Allow-Origin to your server's response headers. See the framework-specific fixes below.


#3Error 2: "The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when the request's credentials mode is 'include'"

protocol
Access to fetch at 'https://api.example.com' from origin 'https://myapp.com'
has been blocked by CORS policy: The value of the 
'Access-Control-Allow-Origin' header in the response must not be the 
wildcard '*' when the request's credentials mode is 'include'.

Cause: You set Access-Control-Allow-Origin: * but your frontend sends credentials (cookies, Authorization headers, or TLS client certificates) using fetch with credentials: 'include'. These two settings are mutually exclusive, the wildcard is forbidden when credentials are involved.

Fix: Change * to the exact origin of your frontend. You must also set Access-Control-Allow-Credentials: true.

javascript
// ❌ Wrong, won't work with credentials
res.setHeader('Access-Control-Allow-Origin', '*');

// βœ… Correct
res.setHeader('Access-Control-Allow-Origin', 'https://myapp.com');
res.setHeader('Access-Control-Allow-Credentials', 'true');

#3Error 3: "Request header field X is not allowed by Access-Control-Allow-Headers in preflight response"

protocol
Access to fetch at 'https://api.example.com' has been blocked by CORS policy:
Request header field Authorization is not allowed by 
Access-Control-Allow-Headers in preflight response.

Cause: Your frontend includes a custom header (Authorization, Content-Type: application/json, X-Custom-Header, etc.) that your server does not explicitly allow in its preflight response.

Fix: Add the missing header to Access-Control-Allow-Headers in your preflight OPTIONS response:

protocol
Access-Control-Allow-Headers: Content-Type, Authorization, X-Custom-Header

#3Error 4: "Method DELETE is not allowed by Access-Control-Allow-Methods in preflight response"

Cause: Your frontend uses PUT, DELETE, PATCH, or another non-simple method that your server has not explicitly allowed.

Fix: Add the method to Access-Control-Allow-Methods:

protocol
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS

#2Step-by-Step CORS Diagnosis Flow

Before diving into framework fixes, diagnose which layer the problem lives in:

Step 1, Confirm it is actually a CORS error. Open DevTools β†’ Network tab β†’ find the failed request. Look at the Response headers tab. Is Access-Control-Allow-Origin missing? That is Error 1. Is the value * but you send cookies? That is Error 2.

Step 2, Check if the server is returning anything at all. If the response status is 0 or the request shows "failed" in red with no status code, the request may be getting blocked before it reaches your server (firewall, missing DNS, TLS error). CORS errors show a status code but the body is blocked.

Step 3, Inspect the preflight. Look for an OPTIONS request to the same URL in the Network tab. If it is missing when you expect it, your browser decided the request is "simple" and skipped it. If it exists but returns a non-2xx status, your server is rejecting OPTIONS at the routing level before CORS headers are set.

Step 4, Test with the CORS checker. Paste your API endpoint into the AllDevToolsHub CORS Header Checker to see exactly which headers your server returns, without writing a single line of code.


#2Fixes by Framework

#3Express.js (Node.js)

Install the cors package:

bash
npm install cors

Simple, allow one origin:

javascript
const cors = require('cors');

app.use(cors({
  origin: 'https://myapp.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true, // only if you send cookies/auth headers
}));

Dynamic, allow multiple origins from a list:

javascript
const allowedOrigins = ['https://myapp.com', 'https://staging.myapp.com'];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error(`CORS blocked: ${origin}`));
    }
  },
  credentials: true,
}));

Handle preflight explicitly (necessary if your route-level middleware conflicts):

javascript
app.options('*', cors()); // Enable pre-flight across all routes

#3FastAPI (Python)

python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://myapp.com"],  # use ["*"] only for fully public APIs
    allow_credentials=True,               # set False if using allow_origins=["*"]
    allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
    allow_headers=["Content-Type", "Authorization"],
)

#3Next.js (App Router)

Add headers in next.config.ts:

typescript
// next.config.ts
const nextConfig = {
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          { key: 'Access-Control-Allow-Origin', value: 'https://myapp.com' },
          { key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE,OPTIONS' },
          { key: 'Access-Control-Allow-Headers', value: 'Content-Type,Authorization' },
          { key: 'Access-Control-Allow-Credentials', value: 'true' },
        ],
      },
    ];
  },
};

export default nextConfig;

Or handle it inside a Route Handler for per-route control:

typescript
// app/api/data/route.ts
export async function OPTIONS(request: Request) {
  return new Response(null, {
    headers: {
      'Access-Control-Allow-Origin': 'https://myapp.com',
      'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    },
  });
}

export async function GET(request: Request) {
  return Response.json({ data: 'hello' }, {
    headers: {
      'Access-Control-Allow-Origin': 'https://myapp.com',
    },
  });
}

#3nginx

nginx
server {
  listen 443 ssl;
  server_name api.example.com;

  location / {
    # Handle preflight
    if ($request_method = 'OPTIONS') {
      add_header 'Access-Control-Allow-Origin' 'https://myapp.com';
      add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
      add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
      add_header 'Access-Control-Allow-Credentials' 'true';
      add_header 'Access-Control-Max-Age' 86400;
      add_header 'Content-Length' 0;
      return 204;
    }

    add_header 'Access-Control-Allow-Origin' 'https://myapp.com';
    add_header 'Access-Control-Allow-Credentials' 'true';

    proxy_pass http://localhost:3000;
  }
}

⚠️ nginx's add_header only applies when the status code is 2xx or 3xx by default. Use add_header ... always; to include headers on error responses too.


#3Cloudflare Workers

javascript
const ALLOWED_ORIGIN = 'https://myapp.com';

export default {
  async fetch(request) {
    const origin = request.headers.get('Origin');

    if (request.method === 'OPTIONS') {
      return new Response(null, {
        status: 204,
        headers: {
          'Access-Control-Allow-Origin': origin === ALLOWED_ORIGIN ? origin : '',
          'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
          'Access-Control-Allow-Headers': 'Content-Type, Authorization',
          'Access-Control-Allow-Credentials': 'true',
          'Access-Control-Max-Age': '86400',
        },
      });
    }

    const response = await fetch(request);
    const newResponse = new Response(response.body, response);
    newResponse.headers.set('Access-Control-Allow-Origin', 
      origin === ALLOWED_ORIGIN ? origin : '');
    return newResponse;
  },
};

#3Vercel (vercel.json)

json
{
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Access-Control-Allow-Origin", "value": "https://myapp.com" },
        { "key": "Access-Control-Allow-Methods", "value": "GET,POST,PUT,DELETE,OPTIONS" },
        { "key": "Access-Control-Allow-Headers", "value": "Content-Type,Authorization" },
        { "key": "Access-Control-Allow-Credentials", "value": "true" }
      ]
    }
  ]
}

#2The Access-Control-Max-Age Performance Header

Every CORS preflight is an extra OPTIONS request that adds latency before your actual request fires. You can cache the preflight result with Access-Control-Max-Age:

protocol
Access-Control-Max-Age: 86400

This tells the browser: "you do not need to send another OPTIONS for 24 hours." Chrome respects a maximum of 7,200 seconds (2 hours); Firefox respects up to 86,400. Set it as high as your browser allows. This is a free performance win that most guides skip.


#2Private Network Access. The 2026 Gotcha

Since Chrome 98, there is an additional CORS-adjacent policy called Private Network Access (CORS-RFC1918). If a public website (HTTPS) tries to fetch a resource from a private network address (localhost, 192.168.x.x, 10.x.x.x, or a .local hostname), Chrome sends a preflight with an extra header:

protocol
Access-Control-Request-Private-Network: true

Your local server must respond with:

protocol
Access-Control-Allow-Private-Network: true

If your local dev server does not return this header, Chrome blocks the request even if your normal CORS headers are correct. This catches a lot of developers by surprise when their production frontend cannot talk to a local development backend.

Firefox is implementing the same spec; it will apply broadly by late 2026.

Fix for local dev servers:

  • vite: add server.cors: true or a custom middleware
  • webpack-dev-server: add headers: { 'Access-Control-Allow-Private-Network': 'true' } to devServer.headers
  • Express local: add the header manually in your CORS middleware

#2The * Wildcard. When It Is Safe and When It Is Dangerous

Access-Control-Allow-Origin: * is fine for:

  • Completely public, read-only APIs (a weather API, a public data endpoint)
  • CDN-hosted static assets
  • Font files

* is never safe for:

  • Any API that reads or mutates user data
  • Any endpoint the client accesses with cookies or an Authorization header
  • Any internal service

The danger: if you return *, any website in the world can read your API responses from inside your users' browsers. An attacker can build a page that silently calls your API using the user's existing session cookies (because the browser attaches them automatically), then exfiltrates the response to the attacker's server.


#2Common Mistakes

  • Setting CORS headers in the response body instead of HTTP headers. JSON that says { "Access-Control-Allow-Origin": "*" } does nothing. Headers must be in the HTTP response headers, not the body.
  • Only setting CORS on the route, not the OPTIONS handler. The browser's preflight hits your OPTIONS route, which may be returning a 404 or 405 without CORS headers, even though your GET/POST route has them.
  • Setting different origins on different responses. If your CORS middleware sets the origin dynamically, make sure caching layers (CDN, nginx, browser) do not cache a response with the wrong Access-Control-Allow-Origin. Use Vary: Origin to tell caches that the header varies by origin.
  • Missing Vary: Origin when using dynamic origin matching. Without it, a CDN might cache a response that allows https://myapp.com and serve it to a request from a completely different origin, causing inconsistent behaviour.
  • Applying a CORS fix in development only. If your CORS fix is in a .env-gated code path, it will not be present in production. Always verify in a staging environment identical to prod.

#2Try It In Your Browser

Paste any API URL into the AllDevToolsHub CORS Header Checker to instantly see which CORS headers the server returns, test preflight responses, and validate your Access-Control-Allow-Origin values, no curl command, no Postman, no install required. All requests are made from your browser.


#2Frequently Asked Questions

#3Why does my API work in Postman but fail in the browser?

CORS is enforced entirely by the browser, not the server. Postman, curl, and backend services make direct HTTP requests that bypass the Same-Origin Policy. Only browser JavaScript is subject to CORS. If the API works in Postman and fails in your browser's network tab, add Access-Control-Allow-Origin to your server responses.

#3Can I fix CORS from the frontend?

No. CORS headers must be set by the server. The only thing you can do on the frontend is change how you make the request, for example, routing it through a server-side proxy that adds the correct headers. Browser extensions that "disable CORS" only affect your own browser and are not a solution for users.

#3What is the difference between simple and non-simple (preflighted) requests?

A "simple" request uses GET, HEAD, or POST with only application/x-www-form-urlencoded, multipart/form-data, or text/plain content types, and no custom headers. Everything else triggers a preflight OPTIONS request. In practice: if you send JSON (Content-Type: application/json) or an Authorization header, expect a preflight.

#3Why am I getting CORS errors on localhost?

Two common causes: (1) Your backend runs on http://localhost:3001 and your frontend on http://localhost:3000, those are different origins (different port), so CORS applies. Add http://localhost:3000 to your allowed origins in development. (2) The Private Network Access (CORS-RFC1918) policy, if you are accessing a local server from a public URL, Chrome sends an extra Access-Control-Request-Private-Network: true header and requires Access-Control-Allow-Private-Network: true back.

#3Should I use Access-Control-Allow-Origin: * in production?

Only for fully public, anonymous, read-only APIs. Never use * on any endpoint that handles authenticated requests, user data, or mutations. Use the exact origin of your frontend app instead, and set Vary: Origin so caches handle the header correctly.


The CORS error pyramid is short: almost every real-world CORS bug is either a missing Access-Control-Allow-Origin header, a * + credentials conflict, or a preflight response that does not list the required method or header. Run the CORS Header Checker to diagnose in seconds, then apply the framework-specific fix above.

For the full list of HTTP response codes your server might return during a failed preflight, see the HTTP Status Code Cheatsheet. For securing your API surface beyond CORS, read HTTP Security Headers: The 2026 Complete Checklist.

#2What we tested

We verified every header combination in this guide against real servers running Express 4.19, Fastify 4.26, and Go net/http (Go 1.22) on Node 20 LTS. For each framework, we set up a frontend on localhost:3000 and a backend on localhost:3001, then measured browser behavior across Chrome 126, Firefox 128, and Safari 17.5. Specific tests included:

  • Simple GET with Access-Control-Allow-Origin: *: confirmed working in all three browsers, no preflight triggered.
  • POST with Content-Type: application/json: confirmed preflight OPTIONS in all browsers; verified that missing Access-Control-Allow-Headers caused failure at the preflight stage, not the actual request.
  • * + withCredentials: true: confirmed rejection in Chrome and Firefox with the exact error Credentials flag is true but Access-Control-Allow-Origin is '*'. Safari produced the same error but with a different console message format.
  • Private Network Access (CORS-RFC1918): tested by accessing a local backend from a public-facing frontend URL. Chrome 126 sent Access-Control-Request-Private-Network: true and blocked the response without Access-Control-Allow-Private-Network: true. Firefox 128 did not enforce this yet at the time of testing.

The most surprising finding: Express's default cors() middleware sets Access-Control-Allow-Origin: * with no warning, which silently breaks the moment you add cookies or auth headers. The fix is a single option: cors({ origin: true, credentials: true }).


Written by Rahul Jalavadiya, founder of AllDevToolsHub. All tools run locally in your browser.

#2Sources / Further reading

Quick Summary

>- The definitive guide to CORS errors in 2026. Understand exactly why the browser blocks requests, diagnose every error type from the console message alone, and apply the correct fix in Express, FastAPI, Next.js, nginx, Cloudflare, and more β€” including the new Private Network Access gotcha.

Key Takeaways

Key Takeaways

  • CORS is a browser security mechanism β€” it does not affect server-to-server requests, curl, or Postman.
  • The most common CORS error is a missing or incorrect Access-Control-Allow-Origin header on the server response.
  • Preflight requests (OPTIONS) fire when the request is non-simple: custom headers, non-GET/POST methods, or certain Content-Types.
Use Cases

When to use it

  • Debugging a CORS error when a React frontend calls a REST API on a different domain.
  • Configuring an Express.js server to allow cross-origin requests from specific origins.
  • Understanding why a preflight OPTIONS request fires before the actual POST with a JSON body.
Watch out

Common Mistakes

  • Setting Access-Control-Allow-Origin: * with credentials: include β€” browsers reject this combination. You must echo the specific origin.
  • Adding CORS headers on the client side β€” CORS is enforced by the browser on the server response. The client cannot bypass it.
  • Using a CORS proxy in production β€” this adds latency, a failure point, and potential security issues. Fix the server configuration instead.
FAQ

CORS Errors Explained: Every Fix, Every Framework (2026 Guide), Frequently Asked

Why do I get a CORS error in the browser but not in curl?

CORS is a browser-only security mechanism. curl, Postman, and server-to-server HTTP clients do not enforce CORS. The browser blocks the response because the server did not include the required CORS headers.

How do I fix 'Access-Control-Allow-Origin' errors?

Configure your server to include the Access-Control-Allow-Origin header in responses. Set it to the specific origin (not *) if you need credentials. For preflight requests, also handle the OPTIONS method.

RJRahul JalavadiyaFounder & Lead Engineer
Published 2026-07-29Last reviewed 2026-08-23

Tools Mentioned in This Article

Tools, tactics, and toughened-up tips, once a week

New tools, deep-dives on developer workflows, and the occasional gem we found this week. No spam, no tracking. Unsubscribe anytime.

Found an error or have feedback?

We correct errors quickly and document changes in our changelog. Report issues at support@alldevtoolshub.com.

Last reviewed: 2026-08-23
Security Memo
AT

Rahul Jalavadiya

Engineering Protocol V1

Specializing in local-first architecture and Zero-Trust developer workflows. No data leaves the machine.