# CORS Errors Explained: How to Fix "No 'Access-Control-Allow-Origin' Header"

You call your API from the browser and the console fills with red: *blocked by CORS policy*. The confusing part is that the request usually *did* reach your server — often it even succeeded. CORS doesn't stop the request; it stops your JavaScript from **reading the response**. That's also why the same call works fine in curl or Postman: CORS is enforced by browsers, and only browsers.

## What counts as cross-origin

An origin is the scheme, host and port together. These are all different origins:

- `https://app.example.com` and `https://api.example.com` — different host
- `http://localhost:3000` and `http://localhost:8080` — different port
- `http://example.com` and `https://example.com` — different scheme

When a page on one origin fetches from another, the browser asks the server for permission through response headers, chiefly `Access-Control-Allow-Origin`.

## Simple requests and preflights

Some requests are sent straight away, and the browser checks the response headers afterwards. Others trigger a **preflight**: an automatic `OPTIONS` request that asks whether the real request is allowed. A preflight happens when, among other things, the request:

- uses a method other than GET, HEAD or POST,
- sends a header outside a short safe list — `Authorization` is the classic one, or
- sends `Content-Type: application/json` (only `text/plain`, `multipart/form-data` and `application/x-www-form-urlencoded` are exempt).

So almost every real API call — JSON body, bearer token — is preflighted. The server must answer that `OPTIONS` request successfully, with the right headers, *before* the browser sends the real one.

## The common errors, and their fixes

**"No 'Access-Control-Allow-Origin' header is present on the requested resource."**
The response didn't include the header. Add it, with the calling origin as the value:

```http
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
```

`Vary: Origin` matters when you choose the value per request: it stops a CDN from serving one origin's response to another.

**"Response to preflight request doesn't pass access control check: It does not have HTTP ok status."**
Your server answered the `OPTIONS` preflight with an error — often [401](/http/401) because auth middleware ran first, or [404](/http/404)/[405](/http/405) because no route handles OPTIONS. Answer preflights before authentication, with [204 No Content](/http/204) and the CORS headers.

**"Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response."**
List every custom header the client sends:

```http
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
```

**"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'."**
Requests that send cookies (`credentials: "include"`) can't use `*`. Return the specific origin plus `Access-Control-Allow-Credentials: true`.

**"Redirect is not allowed for a preflight request."**
The preflight hit a redirect — commonly a trailing-slash or http→https rule. Call the final URL directly.

## Server examples

Express, with the `cors` package and an explicit allow-list:

```js
import cors from "cors";

app.use(cors({
  origin: ["https://app.example.com", "http://localhost:5173"],
  credentials: true,
  maxAge: 600, // cache the preflight for 10 minutes
}));
```

A Next.js route handler answers preflights by exporting `OPTIONS`:

```ts
const CORS = {
  "Access-Control-Allow-Origin": "https://app.example.com",
  "Access-Control-Allow-Methods": "GET, POST",
  "Access-Control-Allow-Headers": "Authorization, Content-Type",
};

export function OPTIONS() {
  return new Response(null, { status: 204, headers: CORS });
}

export async function POST(request: Request) {
  return Response.json({ ok: true }, { headers: CORS });
}
```

nginx in front of an API:

```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 "Authorization, Content-Type" always;
    add_header Access-Control-Max-Age 600 always;
    return 204;
  }
  add_header Access-Control-Allow-Origin "https://app.example.com" always;
  proxy_pass http://127.0.0.1:3000;
}
```

The `always` flag makes nginx add the header to error responses too — without it, a 500 from your API shows up in the browser as a CORS error, which hides the real problem.

## What not to do

- **Don't reflect any origin with credentials.** Echoing whatever `Origin` arrives, together with `Access-Control-Allow-Credentials: true`, lets *any* website read your users' data with their cookies. Check origins against an allow-list.
- **Don't use `mode: "no-cors"`.** It makes the error disappear by giving you an opaque response you can't read. The request "works"; your code gets nothing.
- **Don't fight CORS in development.** Use your dev server's proxy (Vite's `server.proxy`, Next.js `rewrites`) so the browser sees one origin.

## Testing a preflight with curl

curl doesn't enforce CORS, but it can show you exactly what the browser would see:

```bash
curl -i -X OPTIONS https://api.example.com/items \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: authorization, content-type"
```

Look for a 2xx status and the three `Access-Control-Allow-*` headers. To check what a list of endpoints returns, paste them into the [HTTP Status Checker](/tools/web/http-status-checker).

One last tip: a preflight is cached for as long as `Access-Control-Max-Age` says — up to 2 hours in Chrome and 24 hours in Firefox — so after changing CORS headers, test in a fresh private window.

For each header in detail, see the reference pages for [Access-Control-Allow-Origin](/headers/access-control-allow-origin), [Access-Control-Allow-Methods](/headers/access-control-allow-methods), [Access-Control-Allow-Headers](/headers/access-control-allow-headers), [Access-Control-Allow-Credentials](/headers/access-control-allow-credentials) and [Origin](/headers/origin).
