Access to fetch has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header
Filed under APIs, auth & CORS
The fix, fast
CORS is the browser refusing to let your page read a response from another origin because that other server did not send an Access-Control-Allow-Origin header saying your origin is allowed. The fix belongs on the API server, not in your fetch call: configure the server to send that header (and handle the OPTIONS preflight) for your site's origin. If you do not control the API, call it from your own server or through a same-origin rewrite instead.
Why this happens
An origin is the combination of protocol, domain, and port — so http://localhost:3000 and https://api.example.com are different origins, and so are two different localhost ports. For safety, browsers enforce a same-origin rule: JavaScript on one origin cannot read responses from another unless the other server explicitly opts in. That opt-in is CORS, short for Cross-Origin Resource Sharing, and it is a permission slip sent as response headers by the server.
That is why the error feels backwards. Your request often reaches the server and the server may even do the work — the browser blocks your code from reading the reply because the permission header is missing. For requests with custom headers, JSON content types, or an Authorization header, the browser also sends a quiet preflight first: an OPTIONS request asking, in effect, is this real request allowed? If the server does not answer the preflight correctly, the real request never happens. One extra rule catches people out: if your fetch uses credentials like cookies, a wildcard header value of * is not accepted — the server must name your exact origin.
- Missing header: the API never sends Access-Control-Allow-Origin at all.
- Wrong origin listed: the server allows a different domain, protocol, or port than the one you are calling from.
- Preflight not handled: OPTIONS requests fail or lack the allowed methods and headers.
- Credentials mismatch: you send cookies with
credentials: "include"but the server replies with*.
The fix, step by step
- Confirm it in the Network tab before changing code. Open browser DevTools, go to Network, trigger the failing request, and click it. Check the status and the Response Headers for Access-Control-Allow-Origin. Also look for a separate OPTIONS request just before it. This tells you whether the preflight is failing, the header is missing, or it names the wrong origin — three different fixes that look identical in the console.
- Fix it on the server if you control the API. The server receiving the request must send the headers. If you are not the API owner, you cannot fix their CORS from your frontend — skip to step 5 for the proxy route. If it is your Express server, add CORS middleware with your exact frontend origin.
// npm install cors import cors from "cors"; import express from "express"; const app = express(); app.use(cors({ origin: "https://your-site.com", credentials: true, })); - Do the same in a Next.js route handler if that is your API. If your API lives in a Next.js
app/apiroute, return the headers on the response and answer OPTIONS requests directly. Set the origin to your real deployed domain in production, not localhost.const headers = { "Access-Control-Allow-Origin": "https://your-site.com", "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization", }; export async function OPTIONS() { return new Response(null, { status: 204, headers }); } export async function GET() { return Response.json({ ok: true }, { headers }); } - Match credentials exactly — never combine * with cookies. If your fetch includes
credentials: "include", the server must reply with your specific origin (not*) and the headerAccess-Control-Allow-Credentials: true. If you do not actually need cookies or auth headers sent cross-origin, the simpler fix is to remove credentials from the fetch and let a plain origin rule apply. Pick one mode and make both sides agree. - If you do not control the API, call it through your own server. Browsers enforce CORS; servers calling servers do not. Move the call into your own backend or a Next.js route handler and have your frontend call that same-origin endpoint instead. In Next.js you can also add a rewrite so the browser thinks it is calling your own domain while Next.js forwards the request.
// next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { async rewrites() { return [ { source: "/api/external/:path*", destination: "https://api.example.com/:path*", }, ]; }, }; export default nextConfig; - Do not use a browser extension or no-cors as the fix. A CORS-unblock extension only changes your browser, so the site stays broken for every visitor. And
mode: "no-cors"in fetch does not unlock the response — it makes it opaque, meaning your code gets a response it cannot read. Both hide the error without fixing anything. After your real fix, hard-refresh and re-check the Network tab headers to confirm.
Give your AI this prompt
My browser console says: "Access to fetch at [paste URL] from origin [paste your origin] has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource." My frontend origin is: [e.g. http://localhost:3000 / your deployed domain] My API is: [say whether it is my own Express / Fastify / Next.js route handler, or a third-party API I do not control] Here is my fetch code and, from DevTools Network tab, the status and response headers for the failing request and any OPTIONS request: [paste them]. Please diagnose whether the header is missing, names the wrong origin, the OPTIONS preflight is failing, or there is a credentials vs wildcard * mismatch. If it is my server, show the exact server-side change to allow my origin and handle preflight. If it is a third-party API, show me a same-origin server proxy or Next.js rewrite instead. Constraints: do not suggest a browser extension or mode: "no-cors" as a fix, do not change my frontend fetch to hide the error, make one change at a time, and do not rewrite unrelated files.