How to fix a CORS error — 3 causes and the fix for each

The most maddening thing about a CORS error: you spend hours editing the frontend, and the frontend isn't the culprit. So here's the surprise that saves your evening: CORS almost never gets fixed on the frontend. No matter how many headers you add to your fetch, nothing changes. The fix is always on the server or in a proxy.
Let's go by the book: symptom → cause → how to check → how to fix. Starting with the most common.
The symptom
Red in the browser console:
Access to fetch at '...' from origin '...' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Important to grasp: this does not mean the server is down. The request arrived, the response came back — but the browser looked at it and decided not to hand it to your code, because the server didn't allow your origin (domain + port). It's a protection, not a bug.
Cause 1 (most common): the server doesn't send the allow header
Nine times out of ten this is it. Your API responds, but the reply has no Access-Control-Allow-Origin header, and the browser hides the result.
How to check. Open DevTools → Network tab → click the failed request → Response Headers. No Access-Control-Allow-Origin line — diagnosis confirmed.
How to fix. Add the header on the server — where the API lives. If it's your Express:
res.setHeader("Access-Control-Allow-Origin", "https://your-site.com");
Or pull in the ready-made cors package. In Supabase Edge Functions, return the CORS headers in the response by hand. Specify the exact domain, not *, when you send cookies or auth.
Cause 2: the preflight (OPTIONS request) isn't handled
If you send a non-simple request (a PUT/DELETE method, an Authorization header, a JSON body), the browser first sends a preflight request with the OPTIONS method — asking "is this allowed?". If the server doesn't answer OPTIONS properly, the main request never even leaves.
How to check. In Network you'll see an OPTIONS request before your main one — and it's red or missing the needed headers.
How to fix. Teach the server to answer OPTIONS: return status 204 and the headers Access-Control-Allow-Methods (e.g. GET, POST, PUT, DELETE) and Access-Control-Allow-Headers (e.g. Content-Type, Authorization). The cors package or your framework usually does this for you — just don't switch it off.
Cause 3: you're calling someone else's API straight from the browser
You grab a third-party service (weather, exchange rates, someone's model) and call it directly from the frontend. And it doesn't allow your origin — and doesn't have to. It's not your server; you can't fix its headers.
How to check. The request goes to a foreign domain, not yours. The service's docs often say outright "call this from the backend."
How to fix. Reach the foreign API through your own backend (a proxy): the frontend calls your server → your server calls the foreign REST API → returns the answer to the frontend. The browser only talks to your domain — CORS gone. Bonus: the foreign service's key now hides on the server instead of sitting in the browser.
Can I disable CORS on the frontend?
No. CORS is a browser rule, and headers in your fetch don't affect it. It's fixed only on the side that serves the data: your server — add the header; someone else's — proxy it through yours.
Why does it work in Postman but throw a CORS error in the browser?
Because Postman and curl aren't browsers — they don't apply the CORS policy. So "it's fine in Postman" confirms the server is alive and responding; the problem is exactly the browser permission. That's your clue, not a reason to despair.
Will setting Access-Control-Allow-Origin: * help?
Sometimes yes, but carefully: * allows everyone and does not work together with cookies/auth — the browser rejects such a response. For requests with login, specify the exact domain.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.





