BugMojoBugMojoBugMojo
FeaturesPricingBlogHelpAbout
Add to ChromeLog inGet started
BugMojoBugMojo

Bug reports that actually help fix bugs — capture, replay, share.

A product of Softech Infra.

Product

  • Features
  • Pricing
  • Browser extension
  • Get started
  • Log in

Resources

  • Help & guides
  • Blog
  • Compare
  • Glossary

Company

  • About
  • Contact
  • Security
  • Privacy
  • Terms
  • Sitemap
© 2026 BugMojo. All rights reserved.
AllGuidesEngineeringPlaybooksCompareGlossaryAlternativesBy roleBug tracking by framework
  1. Home
  2. Blog
  3. Guides
  4. How to Fix "Failed to Fetch" Errors in JavaScript
Guide

How to Fix "Failed to Fetch" Errors in JavaScript

"TypeError: Failed to fetch" means the fetch() promise rejected — the request never completed at all. Here is why that is different from a 404, what actually stops the request, and how to find the real reason.

Hrishikesh BaidyaHrishikesh Baidya·Jul 16, 2026·8 min read
Guides
Isometric line-art browser with a console error and a network panel showing a failed fetch request, linked in lime, on a dark charcoal canvas
TL;DR
  • What it means: TypeError: Failed to fetch is the fetch() promise rejecting — the request never completed as a network transaction.
  • What it is NOT: it is not an HTTP error. A 404 or 500 still resolves the promise with response.ok === false. "Failed to fetch" means something stopped the request from finishing at all.
  • Common causes: CORS, offline or DNS failure, mixed content (https page requesting http), a wrong or unreachable URL, the server being down, a block from an extension/ad-blocker/CSP, or an aborted request.
  • Find it fast: open the Network tab and read the real reason — the console message is deliberately generic. Then handle offline with try/catch and check response.ok separately from network failure.

Uncaught (in promise) TypeError: Failed to fetch is one of the most frustrating errors in front-end JavaScript, because the message tells you almost nothing. It is the same string whether your API is down, your page is offline, a browser extension ate the request, or you typed the URL wrong. The one thing it always tells you is the most important thing — and almost everyone misses it: the fetch() promise rejected. The request did not come back with a bad answer. It never came back at all.

That single distinction is the whole guide. Once you internalise what "rejected" means for fetch(), the vague message becomes a precise category of problem, and the Network tab hands you the specific cause. Let us make the distinction load-bearing before touching a single fix.

What "Failed to fetch" actually means

TypeError: Failed to fetch means the fetch() promise rejected because the request failed at the network level — it never completed. It is not an HTTP status error: a 404 or 500 still resolves the promise with response.ok === false. "Failed to fetch" specifically means something — CORS, offline, mixed content, a block, a bad URL — stopped the request from completing at all.

MDN is explicit about this: the promise returned by fetch() rejects only when the request cannot be made or the response cannot be retrieved — a genuine network error. It does not reject because the server answered with an error status. If the browser managed to send the request and read a response back, the request completed, and from fetch()'s point of view that is a success, no matter what the status code says.

So "Failed to fetch" is not "the server said no." It is "the browser never got a usable answer." The connection was refused, blocked, redirected into a wall, or cut off. The message is intentionally generic — the browser withholds specifics to avoid leaking cross-origin information — which is exactly why you cannot debug this from the console string alone.

Reject vs. resolve: the distinction that trips everyone up

Here is the trap that costs people hours. A developer writes await fetch(url), wraps it in try/catch, and assumes the catch block will run whenever "something goes wrong." It will not. A 404, a 403, a 500 — none of them throw. The await resolves cleanly, control skips the catch entirely, and the code marches on treating an error page as if it were valid data.

That is because an unsuccessful HTTP response is still a response. The promise resolves with a Response object whose ok property is false (it is true only for statuses in the 200–299 range) and whose status holds the real code. The catch block fires only for the other category — the network-level failure that produces "Failed to fetch." These are two different failure modes, and robust code has to handle both, separately.

the-two-failure-modes.jsjavascript
// Failure mode 1 — the request COMPLETED but with an error status.
// fetch() RESOLVES. The catch below never runs.
const res = await fetch('/api/user/999'); // 404 Not Found
console.log(res.ok);     // false
console.log(res.status); // 404
// ...but no exception was thrown. This is NOT "Failed to fetch".

// Failure mode 2 — the request never COMPLETED (offline, CORS, blocked).
// fetch() REJECTS. This is the one that throws "Failed to fetch".
try {
  await fetch('https://api.down-or-blocked.example/data');
} catch (err) {
  console.log(err.name);    // 'TypeError'
  console.log(err.message); // 'Failed to fetch'
}

The causes of "Failed to fetch"

Because the message is one-size-fits-all, it covers a whole family of network-level failures. These are the ones you will actually hit, roughly in order of frequency:

  • CORS. The browser blocked the request (or its preflight) because the server did not return an Access-Control-Allow-Origin header authorising your origin. This is common enough that it deserves its own playbook — see how to debug CORS errors for reading the failing preflight and applying the exact server-side header. But note: CORS is one cause of "Failed to fetch," not the only one.
  • Offline or DNS failure. The device has no connection, or the hostname cannot be resolved. The request never leaves the machine.
  • Mixed content. An https page requesting an http URL. The browser blocks or upgrades the insecure request; the plain-http version is refused outright.
  • A wrong or unreachable URL. A typo, a stale endpoint, a port nothing is listening on — the connection is refused and nothing comes back.
  • The server is down. The host is reachable but the service is not accepting connections, so there is no response to read.
  • A block by an extension, ad-blocker, or CSP. An ad-blocker or privacy extension cancels the request, or your own Content-Security-Policy connect-src directive forbids the origin.
  • An aborted request. An AbortController fired (a timeout, a component unmounting) and cancelled the request mid-flight.
mixed-content-and-url.jsjavascript
// Mixed content: page is https, request is http → blocked as insecure.
// Fix: request the https URL (or a protocol-relative / same-origin path).
await fetch('http://api.example.com/data');   // blocked → Failed to fetch
await fetch('https://api.example.com/data');  // ok

// Wrong / unreachable URL: nothing is listening → connection refused.
await fetch('http://localhost:3001/api');     // dev server is on :3000
// Fix: validate the base URL and port before shipping.
const API = new URL('/api', window.location.origin); // build it, don't hardcode
Tip

Suspect CORS but not sure? A CORS failure is the one "Failed to fetch" cause the Network tab names outright — you will see a CORS error status and, in the console, a message about a missing Access-Control-Allow-Origin header. If you see that, the fix is almost always server-side; the CORS field guide walks through reading the preflight and adding the right header. If you do not see a CORS message, stop blaming CORS and look at the other causes.

How to find the real reason

The console gives you the category; the Network tab gives you the cause. Open DevTools, switch to Network, and reproduce the request. Find the failing row — its status will read (failed) or CORS error rather than a number — and click into it. What you see there is the actual diagnosis: a CORS message names the missing header, a mixed-content block says the request was blocked as insecure, an ad-blocker or CSP block shows the request cancelled before it went out, and an offline or DNS failure shows a connection that never opened. Two failures that print the identical console string look completely different here.

The most important habit, though, lives in the code itself: check response.ok separately, so you never confuse an HTTP error with a network failure. The example below handles both modes explicitly — the rejected promise (network failure) in the catch, and the resolved-but-unsuccessful response (!response.ok) with its own branch.

correct-fetch-error-handling.jsjavascript
async function getData(url) {
  // Cheap early signal: are we even online?
  if (!navigator.onLine) {
    throw new Error('You appear to be offline. Check your connection.');
  }

  let response;
  try {
    response = await fetch(url);
  } catch (err) {
    // fetch() REJECTED → a network-level failure ("Failed to fetch").
    // CORS, offline, DNS, mixed content, blocked, aborted, server down.
    throw new Error(`Network request failed: ${err.message}`);
  }

  // fetch() RESOLVED, but the HTTP status might still be an error.
  // This is a SEPARATE check — a 404/500 never reaches the catch above.
  if (!response.ok) {
    throw new Error(`HTTP ${response.status} ${response.statusText}`);
  }

  return response.json();
}

// Optional: react to connectivity changes in the UI.
window.addEventListener('offline', () => showBanner('You are offline'));
window.addEventListener('online',  () => hideBanner());

Two branches, two meanings. The catch is where "Failed to fetch" lands — treat it as a connectivity or blocking problem, not a data problem. The !response.ok branch is where 404s and 500s land — the server answered, and you can show a real status. Collapsing the two into one generic "something went wrong" is how a blocked request and a missing record end up indistinguishable to your users and to you.

Key takeaway

The honest takeaway: "Failed to fetch" is a rejection, and a rejection means the request never completed. It is never a 404 or 500 — those resolve. Read the Network tab for the specific cause (CORS, offline, mixed content, blocked, bad URL, server down, aborted), and always check response.ok in a separate branch from your catch, so a network failure and an HTTP error stay two different bugs.

When it only happens for someone else

The Network tab is perfect until the failure is not on your machine. A user reports "the page won't load data," you open your own DevTools, and everything works — because the cause was their ad-blocker, their flaky connection, their corporate proxy stripping the request, or a CORS rule that only bites from their origin. The evidence you need lives in a Network tab you will never see, and all you get is a screenshot of a bare "Failed to fetch."

This is the gap BugMojo closes. The browser extension captures the failing request as a structured network entry — the URL, the method, the status, whether it was blocked, refused, or CORS-rejected — alongside the console error and a replay of what the user did. Instead of a screenshot of a generic message, you get the exact request that failed and the reason it failed, from the environment where it actually happened. And because that capture is handed to an AI coding agent over MCP, the agent reads the real network failure and names the fix, rather than guessing which of eight causes produced the same vague string.

⁓ ⁓ ⁓
See exactly which request failed, and why

Install the free BugMojo extension to capture the failing network request — status, block reason, CORS detail — with the console error and a session replay, so "Failed to fetch" stops being a guessing game. No project setup required.

Install the free extension

Frequently asked questions

Frequently asked questions

Sources

  1. Using the Fetch API — how to make requests, read responses, and handle errors including the difference between a rejected promise and an unsuccessful response — MDN Web Docs (2026)
  2. Window: fetch() method — the returned promise rejects only on a network error, not on an HTTP error status such as 404 or 500 — MDN Web Docs (2026)
  3. Response: ok property — true only for statuses in the 200–299 range; an HTTP error still resolves the fetch with ok set to false — MDN Web Docs (2026)
  4. Mixed content — an https page that requests an http resource has the request blocked or upgraded by the browser — MDN Web Docs (2026)
Share:
Hrishikesh Baidya
Hrishikesh Baidya· Chief Technology Officer

Hrishikesh Baidya is the CTO at Softech Infra. He is drawn to architecture that is invisible — systems that simply work — and leads the engineering behind BugMojo.

On this page

  • What "Failed to fetch" actually means
  • Reject vs. resolve: the distinction that trips everyone up
  • The causes of "Failed to fetch"
  • How to find the real reason
  • When it only happens for someone else

Get bug-tracking insights, weekly.

Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.