TL;DR: A CORS error means your server did not include Access-Control-Allow-Origin in its response headers. The browser blocked the data to protect users. Fix it on the server — add the CORS headers — not in your frontend fetch code. The fix takes 5 minutes. The diagnosis is 2 minutes. Read this guide once and you will never be confused by CORS again.

The Exact Error Messages

CORS errors appear in different forms. Here are the most common and what they mean:

  • Access to fetch at 'http://localhost:5000/api' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    → The server returned no CORS headers at all. Add CORS middleware.
  • ...The 'Access-Control-Allow-Origin' header has a value 'http://localhost:4000' that is not equal to the supplied origin.
    → The server allows a different origin. Update the allowed origins list.
  • ...Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header.
    → The OPTIONS preflight failed. Your CORS middleware is not handling OPTIONS requests.
  • 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'.
    → You used credentials: true with origin: '*'. Change to a specific origin.

The 2-Minute Diagnosis

  1. Open DevTools → Network tab.
  2. Find the failed request (red, or filter by "XHR").
  3. Click it → Response Headers tab. Is Access-Control-Allow-Origin present? If not — CORS not configured. If present — check the value against your frontend origin.
  4. Look for a preflight. Is there an OPTIONS request to the same URL just before the failed request? If yes — your server needs to handle OPTIONS correctly.
  5. Check the Console for the exact error message. The message is specific — read it word-for-word before asking for help.

Framework-Specific Fixes

Express.js

npm install cors

const cors = require('cors');

// Option 1: Simple (allow one origin)
app.use(cors({ origin: 'http://localhost:3000' }));

// Option 2: Multiple origins
app.use(cors({
  origin: ['http://localhost:3000', 'https://myapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));

// Option 3: Dynamic origin (useful for dev + prod)
app.use(cors({
  origin: (origin, callback) => {
    const allowed = [
      'http://localhost:3000',
      'https://myapp.com'
    ];
    if (!origin || allowed.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true
}));

Next.js API Routes (App Router)

// app/api/data/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({ data: 'hello' }, {
    headers: {
      'Access-Control-Allow-Origin': 'https://otherdomain.com',
      'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    }
  });
}

// Handle preflight
export async function OPTIONS() {
  return new Response(null, {
    status: 204,
    headers: {
      'Access-Control-Allow-Origin': 'https://otherdomain.com',
      'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    }
  });
}

Or configure in next.config.js for all routes:

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/api/:path*',
        headers: [
          { key: 'Access-Control-Allow-Origin', value: 'https://otherdomain.com' },
          { key: 'Access-Control-Allow-Methods', value: 'GET, POST, OPTIONS' },
        ],
      },
    ];
  },
};

Flask (Python)

pip install flask-cors

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app, origins=['http://localhost:3000', 'https://myapp.com'])

Fastify

npm install @fastify/cors

import cors from '@fastify/cors';
await fastify.register(cors, {
  origin: ['http://localhost:3000', 'https://myapp.com'],
  credentials: true
});

Vercel Serverless Functions

// api/data.js
export default function handler(req, res) {
  res.setHeader('Access-Control-Allow-Origin', 'https://myapp.com');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

  if (req.method === 'OPTIONS') {
    return res.status(204).end();
  }

  res.json({ data: 'hello' });
}

What Does NOT Fix CORS

Adding headers to fetch() on the frontend

// ❌ This does nothing for CORS
fetch('/api/data', {
  headers: {
    'Access-Control-Allow-Origin': '*'  // Client cannot set this
  }
});

Only the server controls CORS. Headers set in the request do not influence what the server includes in the response.

Using mode: 'no-cors'

// ❌ Hides the error, breaks your code
fetch('/api/data', { mode: 'no-cors' })
  .then(res => res.json())  // ← Throws error: body is locked (opaque response)
  .then(data => console.log(data));  // Never runs

no-cors gives you an opaque response — zero access to the body, status, or headers. It is not a fix.

Disabling CORS in your browser

Using a browser extension or --disable-web-security Chrome flag hides the error for you. Your users still get it. Fix the server.

CORS in Production vs. Development

The best production pattern uses environment variables so the same code works in both environments:

// server.js
const allowedOrigins = process.env.ALLOWED_ORIGINS
  ? process.env.ALLOWED_ORIGINS.split(',')
  : ['http://localhost:3000'];

app.use(cors({
  origin: allowedOrigins,
  credentials: true
}));

// .env (development)
ALLOWED_ORIGINS=http://localhost:3000

// .env.production
ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com

What to Learn Next

Bookmark This

When you hit a CORS error: (1) Read the exact error message. (2) Open DevTools Network, find the request, check response headers. (3) Use the framework fix from this page. That is the complete workflow — it takes under 5 minutes once you know it.

FAQ

A CORS error occurs when a web page makes a request to a different origin and the server does not include the Access-Control-Allow-Origin header permitting that origin. The browser blocks the response to prevent cross-site data theft.

mode: 'no-cors' gives you an opaque response — you cannot read the body, status, or headers. It silences the console error but your code receives no usable data. The real fix is adding CORS headers on the server.

POST requests with JSON bodies trigger a CORS preflight (an automatic OPTIONS request). The server must respond to OPTIONS with the correct headers before the browser sends the actual POST. If the server ignores OPTIONS, the POST fails even if GET works fine.

Extensions bypass CORS in your browser for development testing, but your users' browsers enforce CORS normally. Extensions are useful for confirming that CORS is the actual issue, but the real fix is always server-side configuration.

For Vercel API routes, add CORS headers in the handler function and handle OPTIONS explicitly. For Netlify Functions, set headers in the response object. Both platforms also support a headers config file for applying headers to static responses.