## TL;DR

Bind fetch to the global object before you store, pass, or extract it. Replace a detached reference such as `const f = window.fetch` followed by `f(url)` with `const f = window.fetch.bind(window)`, or wrap it as `const f = (...args) => window.fetch(...args)`, or just call `window.fetch(url)` directly at the call site. The browser throws before any request is sent, so this is not a network, CORS, or server problem.

```text
TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
failed to execute 'fetch' on 'window': illegal invocation
```

## Steps

1. Reproduce with the detached form in a browser console. Run `const f = window.fetch; f('https://example.com/data')`. Expected: it throws Illegal invocation immediately and no request appears in the Network tab. That confirms a receiver problem, not a failed HTTP response.
2. Fix the simplest case by calling fetch as a property of window. Use `window.fetch('https://example.com/data')` instead of a bare extracted reference. Expected: the request appears in the Network tab and returns a promise that resolves or rejects on network grounds, not with Illegal invocation.
3. If code must store or pass fetch around, bind it once where it is captured. Write `this.fetchImpl = options.fetchImpl ?? window.fetch.bind(window)` in a class, or `const boundFetch = globalThis.fetch.bind(globalThis)` in a module. Expected: later calls such as `this.fetchImpl(url)` work because the receiver is already fixed.
4. If the fetch implementation is supplied by a caller or a library option, wrap it instead of binding code you do not own. Use `const safeFetch = (...args) => fetchImpl.call(globalThis, ...args)` when fetchImpl is the native fetch, or simply `const safeFetch = (...args) => window.fetch(...args)`. Expected: passing safeFetch as a callback, a promise argument, or a client option no longer throws.
5. Search the codebase for the usual extraction patterns and fix every hit: destructuring window or globalThis into fetch, a default parameter set to bare fetch, an instance property assigned from fetch, fetch passed directly as a callback, and monkey-patch code that saves the original fetch into a variable before replacing it. Expected: no call site invokes the native function with a class instance, an options object, or undefined as its receiver.
6. Add a regression test that runs in a real browser or with a receiver-enforcing fake fetch, not only in Node. Node fetch (undici) does not brand-check its receiver, so Node and SSR tests pass even with the bug present. Expected: the test fails with Illegal invocation on the old code and passes after binding.

## When to use this skill

- Chrome or Edge shows TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation, and no request reaches the network.
- The code extracts, destructures, stores, or passes window.fetch, globalThis.fetch, or bare fetch as a value before calling it.
- A browser client, SDK wrapper, retry helper, or data pipeline takes a fetch option and calls it as its own method.

## When not to use this skill

- The request appears in the Network tab and fails with CORS, a 4xx or 5xx status, or a TypeError about failed to fetch. Those happen after fetch actually ran.
- The code runs only in Node, Deno, or a worker with its own fetch implementation that is a plain closure. Custom closures usually do not care about their receiver.
- The error names a different Window method or a different interface. The binding fix is similar in spirit, but check that method's own rules first.

## Tool and version compatibility

Applies to native browser fetch in Chrome, Edge, Firefox, and Safari, where fetch is a WebIDL operation on Window (and on WorkerGlobalScope in workers). Firefox words the same failure as fetch called on an object that does not implement interface Window. Bundlers such as webpack do not cause this by themselves; the bug appears when bundled code passes the fetch reference through options, classes, or pipelines that change how it is called.

### Variant: capital Window wording

Chrome reports Failed to execute 'fetch' on 'Window': Illegal invocation with a capital W. It is the same receiver problem and the same bind or wrap fix.

### Variant: fetch saved for monkey-patching or instrumentation

Code that wraps fetch for logging or error reporting often saves the original into a variable and later calls that variable. Bind the saved original, or call it through window, or the wrapper itself becomes the bug.

### Variant: iframe or another realm

A fetch taken from an iframe's contentWindow must be bound to that window, not the top window. Use that frame's own fetch bound to that frame's window, or call it as a property of that window.

## Why this happens

Native fetch is not an ordinary standalone function. The browser checks the receiver (the value of this) when it is called and requires the Window or worker global object. Calling window.fetch(url) sets the receiver to window automatically. Extracting the function into a variable, a class property, or a callback argument changes the receiver to undefined or to the owning object at call time, the check fails, and the browser throws Illegal invocation synchronously, before any network work starts. Node's fetch does not enforce this check, which is why the same code often passes every server-side test.

## Edge cases and pitfalls

- Binding an already custom fetch can change its behaviour if that custom function relied on its own receiver. Wrap only the native fetch, and leave caller-supplied closures alone unless they are the native one.
- Fixing one call site is rarely enough. A client class usually stores fetch once and calls it from many methods, so bind at construction and the methods stay unchanged.
- Do not chase CORS headers, credentials mode, or server logs for this error. The request was never sent.
- The same pattern hits other native Window methods when they are detached, so audit nearby code that extracts timers, observers, or storage helpers in the same style.
