What it means: The browser's fetch() got no readable response: the request failed on the network, or CORS hid the answer.
Free online tool — CORS checker: instant results, no signup.
Sends a cross-origin request and an OPTIONS preflight to your URL and shows the Access-Control-* headers it returns.
TypeError: Failed to fetch is how Chrome, Edge and other Chromium browsers reject a fetch() promise when no usable response arrived; the string is thrown in Blink's fetch_manager.cc. The same failure reads differently in other engines: Firefox throws TypeError: NetworkError when attempting to fetch resource. (Errors.msg, MSG_FETCH_FAILED), and Safari throws TypeError: Load failed, the sanitized description WebKit gives any failed load.
The Fetch standard turns every failure below HTTP into a single «network error», and a network error rejects the promise with a TypeError (fetch.spec.whatwg.org). A response blocked by CORS is deliberately reported the same way, so a script cannot tell «the server is down» from «the server did not allow your origin». MDN lists the other TypeError reasons: an invalid URL, a URL with a username and password, invalid options, a request blocked by a permissions policy, and no connectivity (MDN: fetch()).
What it is not: an HTTP error. A 404 or 500 resolves the promise normally with response.ok === false. A request you cancelled with AbortController rejects with an AbortError DOMException, not this TypeError.
The JavaScript message is generic on purpose. The browser prints the real reason as a separate line in the DevTools Console, just before the TypeError, and marks the request in the Network tab.
| Cause | What the Console or Network tab shows | Fix |
|---|---|---|
| CORS: no allow header for your origin | «... has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present ...» | Send Access-Control-Allow-Origin from the API |
| CORS preflight rejected | «Response to preflight request doesn't pass access control check» | Answer OPTIONS with 2xx and the allow headers |
| DNS lookup failed | net::ERR_NAME_NOT_RESOLVED | Fix the host name or its DNS records |
| Nothing listening on the port | net::ERR_CONNECTION_REFUSED | Start the API or correct the port |
| Mixed content | «Mixed Content: ... requested an insecure resource ... blocked» | Call the API over https:// |
| Ad blocker or privacy extension | net::ERR_BLOCKED_BY_CLIENT | Rename tracking-like paths; test without extensions |
| Untrusted certificate on the API | net::ERR_CERT_AUTHORITY_INVALID and similar | Serve a valid certificate and full chain |
| Device offline or VPN drop | net::ERR_INTERNET_DISCONNECTED or a failed request with no status | Restore connectivity; retry with backoff |
If the request never appears in the Network tab, the browser refused it before sending: an invalid URL, credentials in the URL, a mixed-content block or an extension. If it appears with a status of 200 but the script still gets Failed to fetch, the response arrived and CORS withheld it from the page; that is a server-header problem, not a network one.
Server-side code sees a different text. Node's built-in fetch() comes from undici, which rejects with TypeError: fetch failed and puts the socket-level error in error.cause (undici fetch source). There is no CORS in Node, so the cause is always the network: a refused port, a failed DNS lookup or a connect timeout (undici's connectTimeout defaults to 10 seconds and fails with UND_ERR_CONNECT_TIMEOUT). Log the cause instead of the outer message:
try {
const res = await fetch('https://api.example.com/v1/items');
} catch (err) {
console.error(err.message, err.cause?.code, err.cause?.message);
// fetch failed ECONNREFUSED connect ECONNREFUSED 127.0.0.1:443
}The code then points to the right page: ECONNREFUSED, getaddrinfo ENOTFOUND or ETIMEDOUT.
curl -sS -i https://api.example.com/v1/items -H 'Origin: https://app.example.com' | grep -i '^access-control'No access-control-allow-origin line, or one with a different origin, means the fix is on the API. Details: blocked by CORS policy.curl -sS -i -X OPTIONS https://api.example.com/v1/items \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: PUT' \
-H 'Access-Control-Request-Headers: content-type,authorization'It must answer with a 2xx status and allow the method and headers.dig +short api.example.com, then curl -v https://api.example.com/. A failed lookup, a refused connection or a certificate error in that output is the real cause.https:// cannot call an http:// API; switch the URL to https:// (see mixed content)./ads/, /track or /analytics are common filter-list targets.if (err.name === 'AbortError' || err.name === 'TimeoutError') { /* cancelled or AbortSignal.timeout() */ }
else if (err instanceof TypeError) { /* network or CORS: show offline/retry UI */ }When a web app shows «Failed to fetch» and you did not write it, the useful checks are short: confirm you are online, reload once, turn off ad blockers or privacy extensions for that site, and disconnect a VPN or corporate proxy that may block the API host. If the app works in another browser or on mobile data, the block is on your device or network. If it fails everywhere, the service is down or misconfigured and only its operator can fix it.
curl -sS -o /dev/null -D - https://api.example.com/v1/items \
-H 'Origin: https://app.example.com' | grep -i 'access-control-allow-origin'
# access-control-allow-origin: https://app.example.comThen reload the page with DevTools open: the request should show a normal status in the Network tab and no red CORS or mixed-content line in the Console. To see the CORS answer of any URL, including the preflight, run it through the CORS checker.
The response arrived, but CORS did not allow your origin to read it, so the browser reports a network error to the script. Add Access-Control-Allow-Origin for your origin on the server.
Not necessarily. It covers DNS failures, refused connections, certificate errors, mixed content, blocking extensions and CORS. The DevTools Console line above the error tells which one.
Postman and curl do not enforce CORS or mixed-content rules. The browser does, so a missing Access-Control-Allow-Origin header only breaks the browser request.
Firefox throws TypeError: NetworkError when attempting to fetch resource. Safari throws TypeError: Load failed. They have the same causes.
No. The Fetch standard reports both as the same TypeError on purpose. Check navigator.onLine for connectivity and look at the Console or server logs for CORS.
Free plan — 10 monitors, checks every 5 min, no card required. Upgrade for 1-minute interval and multi-region monitoring.