# Fix: blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource

Your page tried to read a response from another origin, and that server did not say your origin is allowed. The browser received the response and refused to hand it to your code. The fix belongs on the server you are calling: return `Access-Control-Allow-Origin` with your site's origin, and answer the `OPTIONS` preflight request with the same headers.

| Fact | Detail |
|---|---|
| Error | `Access to fetch at 'https://api.example.com/data' from origin 'https://app.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.` |
| Preflight form | `… has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.` |
| Firefox | `Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api.example.com/data. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.` |
| Where you see it | Browser console. Your code only sees `TypeError: Failed to fetch` |
| Usual cause | The API does not send CORS headers, or does not answer `OPTIONS` |
| Usual fix | Add the headers on the server, for the exact origin, including on the preflight |
| BugTape catches it | Yes, as a failed request when it follows a click within 8 seconds. Page-load requests need one line |

## What the error means

An origin is the scheme, host and port together: `https://app.example.com` and `https://api.example.com` are different origins, and so are `http://localhost:3000` and `http://localhost:8000`. Browsers let a page send a request to another origin, but they only let your JavaScript read the response when the server includes `Access-Control-Allow-Origin` with your origin (or `*`) in it. This is CORS, Cross-Origin Resource Sharing.

For requests that are not "simple", such as a `PUT` or `DELETE`, a JSON body with `Content-Type: application/json`, or a custom header like `Authorization`, the browser first sends an `OPTIONS` request called the preflight. If the preflight response lacks the CORS headers, the real request is never sent.

CORS is enforced by the browser. The same request from `curl`, Postman or your server works, which is why the API can look fine when you test it by hand. A proxy or browser extension that disables CORS only changes your own browser.

## Common causes

1. **The API sends no CORS headers at all.** It was built to be called from a server, or from the same origin.
2. **The preflight is not handled.** The `GET` or `POST` handler sets the headers, but `OPTIONS` returns 404, 405, or a response without them. Auth middleware that rejects `OPTIONS` with 401 causes the same thing.
3. **The error response has no headers.** The success path sets CORS headers, but a 500 or 401 from an error handler, proxy or gateway does not. The browser then reports a CORS error that hides the real status.
4. **The origin does not match exactly.** `https://www.example.com` versus `https://example.com`, a different port, or a trailing slash in the configured value.
5. **Credentials with a wildcard.** With `credentials: 'include'`, `*` is not allowed. Chrome prints `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'.`
6. **A redirect.** `http` to `https`, or a missing trailing slash, redirects to a URL that does not send the headers, and redirects on a preflight are not allowed.
7. **You are calling a third-party API from the browser that is meant to be called from a server.** It will never send CORS headers for your site, and it usually needs a secret key you should not put in the browser anyway.

## How to fix it

### Next.js App Router route handler

Set the headers on every response, including errors, and export an `OPTIONS` handler for the preflight. Check the request's `Origin` against a list and send `Vary: Origin` when the value depends on it.

```ts
// app/api/data/route.ts
const ALLOWED = new Set(['https://app.example.com', 'http://localhost:3000']);

function corsHeaders(origin: string | null): HeadersInit {
  if (!origin || !ALLOWED.has(origin)) return { Vary: 'Origin' };
  return {
    'Access-Control-Allow-Origin': origin,
    'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    'Access-Control-Allow-Credentials': 'true',
    Vary: 'Origin',
  };
}

export async function OPTIONS(request: Request) {
  return new Response(null, { status: 204, headers: corsHeaders(request.headers.get('origin')) });
}

export async function GET(request: Request) {
  const headers = corsHeaders(request.headers.get('origin'));
  try {
    return Response.json({ ok: true }, { headers });
  } catch {
    return Response.json({ error: 'failed' }, { status: 500, headers });
  }
}
```

If you do not export `OPTIONS`, Next.js answers it with an `Allow` header and no CORS headers. For many routes, set the headers in Proxy (the file was called Middleware before Next.js 16) or in `headers()` in `next.config.js`.

If your frontend and API are the same Next.js app, call a relative URL (`fetch('/api/data')`). Same-origin requests need no CORS.

### Express with the `cors` package

`app.use(cors(...))` before your routes also answers preflight requests for every route.

```js
import express from 'express';
import cors from 'cors';

const app = express();

app.use(cors({
  origin: ['https://app.example.com', 'http://localhost:3000'],
  credentials: true, // only if the browser sends cookies
}));
app.use(express.json());

app.get('/data', (req, res) => res.json({ ok: true }));
```

Put `cors()` before auth middleware, so a preflight is not rejected with 401 before the CORS headers are added.

### Supabase Edge Functions

Edge Functions do not add CORS headers for you. Answer `OPTIONS` and add the headers to every response, including errors. Recent versions of supabase-js export headers that match what the client sends:

```ts
import { corsHeaders } from 'npm:@supabase/supabase-js@^2/cors';

Deno.serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders });
  }
  try {
    const body = await req.json();
    return Response.json({ hello: body.name }, { headers: corsHeaders });
  } catch {
    return Response.json({ error: 'Invalid JSON' }, { status: 400, headers: corsHeaders });
  }
});
```

### Credentials and the wildcard

If the request sends cookies (`credentials: 'include'`, or `withCredentials` on XHR), the server must echo the exact origin, send `Access-Control-Allow-Credentials: true`, and send `Vary: Origin`. `*` works only for public data without credentials.

```ts
await fetch('https://api.example.com/me', { credentials: 'include' });
```

### Proxy the request through your own server

When you do not control the API, call it from your server and have the browser call your server on the same origin. In Next.js, a rewrite does this:

```js
// next.config.js
module.exports = {
  async rewrites() {
    return [{ source: '/upstream/:path*', destination: 'https://api.example.com/:path*' }];
  },
};
```

`mode: 'no-cors'` does not fix this. It sends the request but gives your code an opaque response it cannot read.

### Find the real status behind the CORS error

Open DevTools, Network, and click the failed request. If the status is 401, 404 or 500, fix that first, then make sure that response also carries the headers. If you only see the `OPTIONS` row failing, the preflight handler is the problem.

## How to catch it in production with BugTape

A CORS failure is invisible to your code apart from `TypeError: Failed to fetch` (Chrome), `NetworkError when attempting to fetch resource.` (Firefox) or `Load failed` (Safari). The browser prints the CORS explanation itself. It does not go through `console`, so it is not in the recorded console output. The BugTape browser SDK records the failed request. When it follows a click within 8 seconds, BugTape reports it as **User-triggered request failed: /data (failed to fetch)** with the request URL and method, the element that was clicked, the replay of the page and the requests around it. Repeats of the same request from the same button become one issue with a count.

A request that runs on page load, on a timer or in the background is recorded in the report's network list but does not start a report. Catch the rejection and pass it to `reportError()`, which fires the window `error` event the SDK listens for. Give the error a message that names the endpoint, so different endpoints become different issues:

```ts
try {
  const res = await fetch('https://api.example.com/data', { credentials: 'include' });
  // …
} catch (err) {
  reportError(new Error(`GET api.example.com/data failed: ${(err as Error).message}`, { cause: err }));
}
```

An unhandled rejection from `await fetch(...)` without a `catch` is also reported, as **Unhandled promise rejection: Failed to fetch**. Requests cancelled with `AbortController` are not reported. The SDK records nothing before `init()` runs.

Set `release` in `init()`. When you mark the issue fixed, a report from a newer release reopens it as a regression, which tells you a deploy brought the missing header back.

```ts
import { init } from '@bugtape/sdk';

init({ apiKey: 'YOUR_PROJECT_KEY', release: import.meta.env.VITE_COMMIT_SHA });
```

Your coding agent can read the issue over MCP (`npx -y bugtape connect claude`, or `cursor` or `codex`) and see the failing URL, method and origin before it edits the server. You review the pull request.

## FAQ

### Why does the request work in Postman or curl but not in the browser?

CORS is a browser rule. Postman, curl and server code do not check `Access-Control-Allow-Origin`, so they show the response. The browser checks it and blocks your page from reading the response when the header is missing or does not match.

### Can I fix CORS in my frontend code?

No. The server you call decides which origins may read its responses. From the frontend you can only avoid the cross-origin call, by calling your own server on the same origin and letting it call the API.

### Is Access-Control-Allow-Origin: * safe?

It is fine for public data that needs no login. It does not work with cookies or other credentials, and it lets any site read the response. For a private API, list the exact origins you trust.

### Why do I get a CORS error when the server actually returned 500?

Error responses often come from a different code path, a proxy or a gateway that does not add the CORS headers. The browser then blocks the response and reports CORS instead of the status. Add the headers to error responses too, and check the status in the DevTools Network tab.

## Sources

- [MDN: Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) and [MDN: CORS header 'Access-Control-Allow-Origin' missing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS/Errors/CORSMissingAllowOrigin), checked 2 Nov 2026.
- [Firefox security.properties](https://github.com/mozilla-firefox/firefox/blob/main/dom/locales/en-US/chrome/security/security.properties) (`CORSMissingAllowOrigin2`), checked 2 Nov 2026.
- [Chromium CORS error strings](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/loader/cors/cors_error_string.cc), checked 2 Nov 2026.
- [Next.js route.js reference](https://nextjs.org/docs/app/api-reference/file-conventions/route) (CORS and `OPTIONS`), checked 2 Nov 2026.
- [expressjs/cors README](https://github.com/expressjs/cors), checked 2 Nov 2026.
- [Supabase: CORS support for Edge Functions](https://supabase.com/docs/guides/functions/cors) and [supabase-js `cors.ts`](https://github.com/supabase/supabase-js), checked 2 Nov 2026.
- [MDN: Window.fetch()](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) (exceptions), checked 2 Nov 2026.

## Related errors

- [TypeError: Failed to fetch](/guides/typeerror-failed-to-fetch/): `TypeError: Failed to fetch`
- [Mixed Content: request blocked](/guides/mixed-content-blocked/): `Mixed Content: The page was loaded over HTTPS, but requested an insecure resource. This request has been blocked`
- ["Script error." from cross-origin scripts](/guides/script-error-cross-origin/): `Script error.`
- [Cannot read properties of undefined](/guides/cannot-read-properties-of-undefined/): `TypeError: Cannot read properties of undefined (reading 'map')`
- [Unexpected token < is not valid JSON](/guides/unexpected-token-not-valid-json/): `SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON`

All guides: [Fix an error](/guides/).

[Start free](/console/register?utm_source=bugtape_seo&utm_campaign=error-guides). Free plan · No credit card · Set up in minutes. Or [try the live demo](/demo/?from=landing&start=1#capture) first.
