What this means
Chrome reports: Access to fetch at '...' from origin '...' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Firefox words it as: Reason: CORS header 'Access-Control-Allow-Origin' missing. Browsers stop scripts from reading responses from another origin (a different scheme, domain or port) unless the server opts in with that header. The request usually reached the server. The browser is refusing to hand the response to your code.
Why it happens
- The API never sets CORS headers. Generated backends often work when tested with curl or on the same origin and fail from the deployed frontend.
- The allow-list names the wrong origin: http instead of https, a missing www, a trailing slash, or localhost only.
- The preflight OPTIONS request is not handled. Requests with JSON bodies or an Authorization header trigger a preflight, and a 404 or 405 on OPTIONS produces this error.
- The server crashed or returned an error page from a proxy or host, and that error response carries no CORS headers. The real bug is the 500, not CORS.
- You are calling a third-party API that does not allow browser calls at all, often because it expects a secret key that should not be in a browser.
How to fix it
- 1Open the Network tab, click the failed request and read the status code. If it is a 4xx or 5xx, fix that error first.
- 2On the server that serves the API, return Access-Control-Allow-Origin with the exact origin of your site. Keep an allow-list for production, preview and local development.
- 3When you echo one origin from a list, also send Vary: Origin so caches do not mix responses.
- 4Handle OPTIONS requests and return Access-Control-Allow-Methods and Access-Control-Allow-Headers for what your frontend sends.
- 5If you send cookies, set Access-Control-Allow-Credentials: true and never combine it with a wildcard origin. Browsers reject that combination.
- 6If you do not control the API, call it from your own server route and have the browser call your route instead. This also keeps the third party's key out of the browser.
const allowedOrigins = new Set([
'https://www.yourapp.com',
'https://yourapp.com',
'http://localhost:3000',
]);
function corsHeaders(origin: string | null): HeadersInit {
if (!origin || !allowedOrigins.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',
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) {
return Response.json(
{ ok: true },
{ headers: corsHeaders(request.headers.get('origin')) },
);
}How to confirm the fix
Send your own API a request with an Origin header and read the response headers. Your real origin should be echoed back. An origin you do not own should get no Access-Control-Allow-Origin header.
curl -s -D - -o /dev/null https://api.yourapp.com/items \
-H 'Origin: https://www.yourapp.com'
curl -s -D - -o /dev/null -X OPTIONS https://api.yourapp.com/items \
-H 'Origin: https://www.yourapp.com' \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: content-type, authorization'
curl -s -D - -o /dev/null https://api.yourapp.com/items \
-H 'Origin: https://not-your-site.example'Frequently asked questions
Why does the request work in curl or Postman but not in the browser?
CORS is enforced by browsers only. Other clients ignore it. That is also why CORS is not a security control for your API: it protects users' browsers, not your server.
Can I fix this in my frontend code?
No. The header must come from the server that owns the resource. The frontend options are to call your own backend instead, or to use a development proxy locally.
Is Access-Control-Allow-Origin: * safe?
For public data that needs no login, yes. For anything tied to a user session, use an explicit allow-list.
I get this error from a Supabase Edge Function. What is different?
Edge Functions do not add CORS headers for you. Handle the OPTIONS request and add the headers to every response, including error responses, as the Supabase CORS guide shows.