Skip to content
RU

TypeError: Failed to fetch — Why fetch() Rejects and How to Fix It

What it means: The browser's fetch() got no readable response: the request failed on the network, or CORS hid the answer.

  1. Read the DevTools Console line above it
  2. Check the request in the Network tab
  3. CORS error: send Access-Control-Allow-Origin
  4. Call https:// APIs from https:// pages
  5. Disable blocking extensions, then retry

Test your URL now

Sends a cross-origin request and an OPTIONS preflight to your URL and shows the Access-Control-* headers it returns.

Check your site's CORS →

What "Failed to fetch" means

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.

Causes and how to tell them apart

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.

CauseWhat the Console or Network tab showsFix
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 failednet::ERR_NAME_NOT_RESOLVEDFix the host name or its DNS records
Nothing listening on the portnet::ERR_CONNECTION_REFUSEDStart the API or correct the port
Mixed content«Mixed Content: ... requested an insecure resource ... blocked»Call the API over https://
Ad blocker or privacy extensionnet::ERR_BLOCKED_BY_CLIENTRename tracking-like paths; test without extensions
Untrusted certificate on the APInet::ERR_CERT_AUTHORITY_INVALID and similarServe a valid certificate and full chain
Device offline or VPN dropnet::ERR_INTERNET_DISCONNECTED or a failed request with no statusRestore 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.

In Node.js the message is "fetch failed"

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.

How to fix it as the developer

  1. Read the line above the TypeError. Open DevTools (F12), Console tab, reload, and copy the first red line about the request. It names CORS, mixed content, DNS or a blocked request.
  2. Repeat the request outside the browser, with an Origin header. curl does not enforce CORS, so it shows what the server really returns:
    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.
  3. Test the preflight if the request sends JSON, custom headers or a method other than GET, HEAD or POST:
    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.
  4. If curl fails too, it is the network. Check name and port: 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.
  5. Remove mixed content. A page served over https:// cannot call an http:// API; switch the URL to https:// (see mixed content).
  6. Rule out extensions. Retry in a private window with extensions off. Paths such as /ads/, /track or /analytics are common filter-list targets.
  7. Handle the failure in code. Distinguish an abort from a network failure so users see a useful message:
    if (err.name === 'AbortError' || err.name === 'TimeoutError') { /* cancelled or AbortSignal.timeout() */ }
    else if (err instanceof TypeError) { /* network or CORS: show offline/retry UI */ }

If you are a visitor of the site

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.

How to confirm the fix

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.com

Then 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.

Related errors

Learn more

Sources

Frequently Asked Questions

Why does fetch say Failed to fetch when the server returned 200?

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.

Does Failed to fetch mean the server is down?

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.

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

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.

What is the Firefox and Safari version of Failed to fetch?

Firefox throws TypeError: NetworkError when attempting to fetch resource. Safari throws TypeError: Load failed. They have the same causes.

Can I catch Failed to fetch and tell CORS from a network failure?

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.

Try the live tool that powered this guide

Free plan — 10 monitors, checks every 5 min, no card required. Upgrade for 1-minute interval and multi-region monitoring.