matevalidate.com Scan your site free →

← All guides

“TypeError: Failed to fetch” — and the CORS wall behind it

TypeError: Failed to fetch

and, often right beside it:

Access to fetch at 'https://api.example.com/…' from origin
'https://your-app.netlify.app' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.

Failed to fetch is the browser saying no usable response came back — it is a family of failures, not one. The good news: the console and the Network tab distinguish them precisely, and each has a specific fix.

First, identify which failure you have

Open F12 → Network, reload, click the red request, and match:

What you see What it is
A second console line naming CORS policy CORS — the server did not permit your site's origin. Section below.
net::ERR_CONNECTION_REFUSED / ERR_NAME_NOT_RESOLVED The URL points at nothing — a dead server, or a localhost address left in deployed code.
Mixed Content in the message An https:// page calling an http:// URL; browsers block it. Fix the URL to https.
net::ERR_BLOCKED_BY_CLIENT An ad blocker or privacy extension killed the request. Test in a private window with extensions off before touching code.
The request shows a status like 401/403/404/500 The fetch worked. You have an API error, not a fetch failure — if it is Supabase saying 401/403, see the RLS guide.

CORS, in the two paragraphs that matter

A browser will not let JavaScript on your-app.com read responses from api.other.com unless that server answers with a header saying your origin is welcome (Access-Control-Allow-Origin). For requests beyond the simple ones — a JSON POST, an Authorization header — the browser first sends a preflight OPTIONS request, and the real request only happens if the server answers the preflight correctly.

The consequence people fight instead of accepting: CORS is fixed on the server being called, never in the calling page. Every frontend "fix" — and generated code tries them all — is either cosmetic or a proxy in disguise.

The fixes that hold

Your own backend is being called (your API, a Supabase edge function): configure it to allow your site's origins — the deployed domain and http://localhost:5173 for development. Wildcards (*) work for public data but are refused by browsers when the request carries credentials; list real origins.

A third-party API is being called directly from the browser (OpenAI, Stripe, a weather API…): this is the AI-built app special, and it is broken twice —

  1. Most such APIs deliberately do not allow browser origins, so CORS blocks the call.
  2. The request needs a secret key, which the browser bundle exposes to every visitor. Even if the call worked, you have published your key. See what must never be a VITE_ variable.

The one fix for both: move the call server-side — a small backend route or an edge function that holds the key, calls the API, and answers your frontend. The browser then talks only to your own origin, and CORS disappears along with the leak.

It works locally, fails deployed: the server allows localhost but not the deployed domain. Add the production origin to the allowed list — and next time a domain changes, remember this paragraph.

Hand it to the AI

Paste this to the AI that built your app

A request in my deployed app fails. From the Network tab:

- URL: [the request URL]
- Status: [status, or "(failed)"]
- Console message: [paste the exact CORS / mixed-content / refused line]
- The page's origin: [https://your-deployed-domain]

Tell me which case this is: CORS not allowing this origin, a dead or
localhost URL, mixed content, or an API that should never be called from a
browser. Then apply the real fix: configure allowed origins server-side for
my own API, or move third-party calls behind a backend/edge function so no
secret key ships to the browser. Do not add a public "CORS proxy", and do not
disable security headers.

Prove it

In a private window, with the console open, run the flow that failed: the request shows a green status, the console shows no CORS line, and — for the third-party case — view-source on the deployed bundle no longer contains the secret key. Postman proves nothing here; only the browser enforces CORS, so only the browser can certify the fix.

Questions people also ask

Is 'Failed to fetch' the same as a CORS error?

Not always. Failed to fetch means the browser got no usable response — CORS is one cause, but so are a wrong URL, a server that is down, mixed content (an http call from an https page) and an ad blocker. The console line right next to it usually says which.

Can I fix CORS from the frontend?

No — that is the point of it. CORS is permission granted by the server being called. The frontend can only change who does the calling: either the API's owner allows your site's origin, or your own backend makes the call and the browser talks only to you.

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

CORS is enforced by browsers only. Command-line tools skip it entirely, which makes them useless for proving a CORS problem is fixed — the browser console is the only referee that counts.