GuidesFAILED TO FETCH

How to fix TypeError: Failed to fetch

TypeError: Failed to fetch means fetch() never got a response it was allowed to read; of the 14 causes we tested, a missing CORS header, a failed preflight, mixed content, a CSP block, a bad certificate and a dropped connection all threw this same error. The console line next to it names the real cause, and when that cause is a third-party API without CORS headers, putting https://proxy.cors.dev/ in front of the URL fixes it. We captured each cause in Chromium 153, Firefox 155 and WebKit 26.6 on 26 October 2026.

What each browser throws

  • Chromium 153 (Chrome, Edge): TypeError: Failed to fetch
  • Firefox 155: TypeError: NetworkError when attempting to fetch resource.
  • WebKit 26.6 (Safari): TypeError: Load failed

Each engine threw its one message for every CORS and connection failure we tried, and error.cause was undefined in all 3, so your code cannot tell the causes apart. That is deliberate: as MDN puts it, the details of a CORS failure are not available to JavaScript for security reasons, and the console is the only place to read them.

Three cases differ. AbortController.abort() rejects with an AbortError. AbortSignal.timeout() rejects with a TimeoutError in Chromium and Firefox, but with AbortError: Fetch is aborted in WebKit 26.6, so check signal.reason to spot a timeout in Safari. And when a server drops the connection mid-body, fetch() resolves and await response.json() throws.

The console line for 14 causes

What fetch() throws and what the console says, per cause and engine (Playwright 1.63, two-origin local test server), checked October 2026
CauseChromium 153Firefox 155WebKit 26.6
What fetch() rejects withTypeError: Failed to fetchTypeError: NetworkError when attempting to fetch resource.TypeError: Load failed
No Access-Control-Allow-Origin headerhas been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.(Reason: CORS header 'Access-Control-Allow-Origin' missing). Status code: 200.Origin http://127.0.0.1:8001 is not allowed by Access-Control-Allow-Origin. Status code: 200
Wildcard * with credentials: 'include'must not be the wildcard '*' when the request's credentials mode is 'include'.(Reason: Credential is not supported if the CORS header 'Access-Control-Allow-Origin' is '*').Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.
Preflight answered 405 without CORS headersResponse to preflight request doesn't pass access control check(Reason: CORS header 'Access-Control-Allow-Origin' missing). Status code: 405.Preflight response is not successful. Status code: 405
Preflight does not allow AuthorizationRequest header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.(Reason: header 'authorization' is not allowed according to header 'Access-Control-Allow-Headers' from CORS preflight response).Request header field Authorization is not allowed by Access-Control-Allow-Headers.
Host name does not resolvenet::ERR_NAME_NOT_RESOLVED(Reason: CORS request did not succeed). Status code: (null).Error resolving "...": Name or service not known
Port closed, connection refusednet::ERR_CONNECTION_REFUSED(Reason: CORS request did not succeed). Status code: (null).Could not connect to localhost: Connection refused
Self-signed or expired certificatenet::ERR_CERT_AUTHORITY_INVALID or net::ERR_CERT_DATE_INVALID(Reason: CORS request did not succeed). Status code: (null).Unacceptable TLS certificate
Server closes the connectionnet::ERR_EMPTY_RESPONSE(Reason: CORS request did not succeed). Status code: (null).Connection terminated unexpectedly
http:// URL from an https:// pageMixed Content: ... requested an insecure resource ... This request has been blockedBlocked loading mixed active content[blocked] ... requested insecure content ... This content was blocked
CSP connect-src 'self'violates the following Content Security Policy directive: "connect-src 'self'". The action has been blocked.Content-Security-Policy: The page's settings blocked the loading of a resource (connect-src)Refused to connect to ... because it does not appear in the connect-src directive
https:// site calling http://localhostPermission was denied for this request to access the loopback address space.Local Network Access permission required; fetch waits for the promptBlocked as mixed content
Connection drops mid-bodyfetch() resolves 200, then response.json() rejects: TypeError: Failed to fetchresponse.json() rejects: TypeError: Content-Length header of network response exceeds response Body.response.json() rejects: TypeError: Load failed
AbortController.abort()AbortError: signal is aborted without reasonAbortError: The operation was aborted.AbortError: Fetch is aborted
AbortSignal.timeout()TimeoutError: signal timed outTimeoutError: The operation timed out.AbortError: Fetch is aborted

Firefox logged DNS failures, refused connections, certificate errors and dropped connections with the same CORS request did not succeed line, although no CORS check ran; that message has its own guide. Our WebKit was Playwright's Linux build, so its network-level lines come from the Linux network stack, and Safari on macOS and iOS words those lines differently.

The localhost row is recent. Since Chrome 142, a public site that calls http://localhost or a private IP needs the visitor's Local Network Access permission; our headless Chromium 153 denied it without a prompt and reported a CORS error. Firefox 155 logged Local Network Access permission required and the fetch was still pending 25 seconds later. WebKit blocked the same call as mixed content.

Read the real cause in DevTools

Open the Console first: the line next to the error is the diagnosis. Then check the Network tab. In Chrome the Status column shows CORS error for CORS failures, (blocked:csp), (blocked:mixed-content), (failed) with the net::ERR_ code for connection problems, and (canceled) for aborts. In Firefox the Transferred column shows CORS Missing Allow Origin, Mixed Block or CSP, and the Headers panel says Response body is not available to scripts. A CORS failure often shows a real status such as 200: the server answered, and the browser kept the body from your script.

Tell CORS from a network failure in code

Tell CORS apart from network failures (tested in Chromium 153, Firefox 155, WebKit 26.6)
const cspBlocked = new Set();
document.addEventListener('securitypolicyviolation', (event) => cspBlocked.add(event.blockedURI));

async function fetchOrExplain(url, options = {}) {
  try {
    return await fetch(url, options);
  } catch (error) {
    if (error.name === 'AbortError' || error.name === 'TimeoutError') throw error;

    const target = new URL(url, location.href);
    let reason = 'network';
    if (!navigator.onLine) {
      reason = 'offline';
    } else if (location.protocol === 'https:' && target.protocol === 'http:') {
      reason = 'mixed-content';
    } else {
      await new Promise((resolve) => setTimeout(resolve, 0));
      if (cspBlocked.has(target.href)) {
        reason = 'csp';
      } else {
        // Any HTTP answer to a no-cors request comes back opaque: the server is up, so CORS blocked the read
        const probe = await fetch(target, { mode: 'no-cors', credentials: 'omit', cache: 'no-store' }).catch(() => null);
        if (probe?.type === 'opaque') reason = 'cors';
      }
    }
    throw Object.assign(new Error(`${reason}: ${error.message}`, { cause: error }), { reason });
  }
}

const results = [];
for (const url of ['https://api.github.com/zen', 'https://xkcd.com/info.0.json', 'https://no-such-host.invalid/']) {
  try {
    const response = await fetchOrExplain(url);
    results.push({ url, result: `HTTP ${response.status}` });
  } catch (error) {
    results.push({ url, result: error.reason ?? error.name });
  }
}
console.table(results);

After a TypeError, the wrapper requests the same URL with mode: 'no-cors'. Any HTTP answer to that probe, even a 404 or 405, comes back opaque, so response.type === 'opaque' proves the server is reachable and the failure was CORS. If the probe also rejects, the problem is DNS, the connection or TLS. Before probing, it checks navigator.onLine (only false is meaningful), an https: page calling an http: URL, and a securitypolicyviolation event for the URL.

We ran it against 18 cases in each engine, 54 runs in total, and every run returned the expected label. The probe costs one extra GET without cookies, and on a flaky network it can succeed after a transient failure and report cors.

Fix it by cause

  1. CORS on your own API

    send Access-Control-Allow-Origin on every response, including errors, as shown in Access to fetch has been blocked by CORS policy. With credentials: 'include', send the exact origin instead of *.

  2. Failed preflight

    answer OPTIONS with 204 and list your methods and headers, such as Authorization, in the allow headers; see CORS preflight failed.

  3. Third-party API without CORS headers

    prefix the URL with https://proxy.cors.dev/. https://xkcd.com/info.0.json sends no CORS header; through the proxy it returned 200 and all 264 bytes in all 3 engines. The free tier costs $0 with no key and no signup: GET and HEAD to any public HTTPS host, text responses up to 1 MiB, a 10-second deadline and 600 requests per minute per IP. Errors carry an X-Cors-Error header. POST, custom headers and Authorization need Pro at $5 per month.

  4. Mixed content

    serve the API over HTTPS.

  5. CSP

    add the API origin to your page's connect-src directive.

  6. Certificate

    fix it on the server; for local development, trust your development certificate.

  7. DNS or refused connection

    check the host name, the port and that the server is running; localhost CORS errors covers local setups.

  8. Abort or timeout

    your own code cancelled the request; raise the timeout or stop aborting.

Third-party API without CORS headers: fetch it through the proxy
// xkcd.com sends no Access-Control-Allow-Origin header, so a direct fetch fails with "cors"
const target = 'https://xkcd.com/info.0.json';
const response = await fetch('https://proxy.cors.dev/' + target, { credentials: 'omit' });

if (!response.ok) {
  throw new Error(response.headers.get('X-Cors-Error') ?? `HTTP ${response.status}`);
}
const comic = await response.json();
console.log(comic.num, comic.safe_title);

The proxy only fetches public HTTPS hosts, so it cannot reach an http:// API, localhost or a private network, and it cannot repair a bad certificate, a dead server or a CSP rule on your own page.

Good questions.

Why does the Network tab show 200 when fetch says Failed to fetch?

The server answered, so DevTools shows the real status, but the response failed the CORS check, so the browser kept the body from your script.

What does NetworkError when attempting to fetch resource mean?

It is the Firefox wording of the same TypeError. In our test Firefox 155 used it for CORS blocks, mixed content, CSP, certificate errors, DNS failures and refused connections; the console line tells them apart.

What does Load failed mean in Safari?

Load failed is the WebKit message for every failed fetch(), and for a body that breaks off during response.json(). The Web Inspector console shows the cause.

Can I read the real reason from the error object?

No. In Chromium 153, Firefox 155 and WebKit 26.6 the name, the message and the empty cause were identical across CORS and network causes. Use the no-cors probe above, or read the console.

Does mode: 'no-cors' fix Failed to fetch?

No. It returns an opaque response with status 0 and a body your code cannot read.

Does a CORS proxy fix every Failed to fetch?

No. It fixes public HTTPS APIs that send no CORS headers. It cannot fix a dead server, a bad certificate, an http:// URL, localhost or the CSP of your own page.