# Removing a service worker safely (kill switch)

> What registration.unregister() removes, why already-controlled pages keep the old worker until they unload, and how a replacement worker clears its own caches.

A service worker that answers requests from a cache keeps answering them after the server is fixed, because the worker, not the server, decides what a controlled page receives. Removing the registration is the repair, and `ServiceWorkerRegistration.unregister()` is the call that performs it.

## How it works

The removal is two separate things that are easy to confuse: dropping the registration, and releasing the pages the old worker still controls. The specification separates them explicitly, and the second one is not under the page's control.

### What unregister() resolves with

`unregister()` takes no arguments and returns a promise. The promise resolves to `false` when no registration was found, and otherwise to `true` "irrespective of whether unregistration happened or not (it may not unregister if someone else just called `ServiceWorkerContainer.register()` with the same scope.)"; the service worker finishes any ongoing operations before it is unregistered ([ServiceWorkerRegistration: unregister() method](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/unregister), developer.mozilla.org). MDN records the method as Baseline widely available, with support across browsers since April 2018, and as available only in secure contexts in some or all supporting browsers.

The specification's `Unregister` algorithm shows why the resolved value says so little about the outcome. It gets the registration for the job's `storage key` and `scope url`; if that is null it resolves the job promise with false and finishes. Otherwise it removes the `registration map` entry for that key and scope, resolves the job promise with true, and invokes `Try Clear Registration` ([Service Workers](https://w3c.github.io/ServiceWorker/), w3c.github.io). So `true` reports that the map entry was removed, not that the worker has stopped.

### Why open pages keep the old worker

The clearing is deferred. The specification notes that when `Try Clear Registration` does not trigger `Clear Registration` at that point, "Clear Registration is tried again when the last client using the registration is unloaded or the extend lifetime promises for the registration's service workers settle". The note on the method itself is blunter: the registration containing the controlled client's active service worker "is effective until all the service worker clients (including itself) using this service worker registration unload. That is, the `unregister()` method only affects subsequent navigations."

That is the behaviour that makes a kill switch feel broken. The tab that ran the call still has its old controller, so its next fetch still reaches the old handler. A reload, or closing the tab, is what ends the registration's effect.

### What unregister() does not remove

The `Unregister` algorithm operates on the `registration map` and the registration's workers. Cached responses are reached through a different interface: `CacheStorage.keys()` resolves with an array of strings naming every `Cache` object the `CacheStorage` tracks, in the order they were created ([CacheStorage: keys() method](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage/keys), developer.mozilla.org), and `CacheStorage.delete()` finds the `Cache` matching the `cacheName`, deletes it and resolves to `true`, or resolves to `false` when no such cache is found ([CacheStorage: delete() method](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage/delete), developer.mozilla.org). Cleanup can delete an explicit list of known cache names directly; `keys()` is needed when those names must first be discovered.

What `keys()` returns is every cache name in that `CacheStorage` object's relevant name-to-cache map, not a list attributed to the removed worker. The caches to delete are the ones the application being removed owns, which is why the examples below match an application-specific prefix instead of the full list.

## Examples

The first example removes registrations from a page that still loads; the second covers the case where the broken worker is the only thing the browser will run.

### Unregistering every registration for the current client's storage key

`navigator.serviceWorker.getRegistrations()` gets all the `ServiceWorkerRegistration` objects associated with a `ServiceWorkerContainer`, in an array ([ServiceWorkerContainer: getRegistrations() method](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/getRegistrations), developer.mozilla.org), which is what lets one call cover a worker registered at a scope the current page does not know about.

```js
// Every cache this application creates is named with this prefix, so only names
// carrying it may be deleted here.
const OWNED_CACHE_PREFIX = 'app-';

async function removeAllWorkers() {
  if (!('serviceWorker' in navigator)) {
    // No worker API in this context: there is nothing registered to remove.
    return { supported: false, removed: 0 };
  }
  const registrations = await navigator.serviceWorker.getRegistrations();
  const results = await Promise.all(registrations.map((reg) => reg.unregister()));
  const names = await caches.keys();
  const owned = names.filter((name) => name.startsWith(OWNED_CACHE_PREFIX));
  await Promise.all(owned.map((name) => caches.delete(name)));
  return { supported: true, removed: results.filter(Boolean).length, cachesDeleted: owned.length };
}
```

The unsupported branch returns a structured result because this context does not expose the Service Worker API. Pages already controlled by the old worker keep it until they unload, so the caller still has to reload them. The prefix filter is what keeps the cleanup to this application; substitute the naming scheme the removed worker actually used, and keep an explicit list of names instead if the scheme has no common prefix.

### Deploying a worker that unregisters itself

When the deployed worker serves a stale copy of the page that would run the previous example, the replacement has to arrive as a service worker. `self.registration` returns a reference to the worker's own `ServiceWorkerRegistration` object ([ServiceWorkerGlobalScope: registration property](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/registration), developer.mozilla.org), and `skipWaiting()` lets the new worker activate without waiting for the old one's clients to close ([ServiceWorkerGlobalScope: skipWaiting() method](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/skipWaiting), developer.mozilla.org).

```js
// sw.js, deployed in place of the worker being removed
const OWNED_CACHE_PREFIX = 'app-';

self.addEventListener('install', () => {
  self.skipWaiting();
});

self.addEventListener('activate', (event) => {
  event.waitUntil((async () => {
    const names = await caches.keys();
    await Promise.all(
      names
        .filter((name) => name.startsWith(OWNED_CACHE_PREFIX))
        .map((name) => caches.delete(name)),
    );
    await self.registration.unregister();
  })());
});
```

This script registers no `fetch` handler, so once it is the active worker its clients go to the network on their next navigation. The registration itself is removed by the `unregister()` call, and the clearing of the registration's workers happens when the last client using it unloads, per the specification note above.

:::caution
Dropping the filter, by running `names.map((name) => caches.delete(name))` over the full result of `caches.keys()`, deletes every named cache in that `CacheStorage` object's relevant map, not only caches owned by this worker. For the kill switch itself, keep the deletion limited to cache names the application owns. [Clear-Site-Data](/reference/storage/clearing-data/) is the server-driven option when a broader reset is the goal.
:::

:::observed
Chrome DevTools, with the English UI, exposes the same operation in Application › Service workers: the button text is `Unregister`, and its tooltip reads `Unregister service worker`. Both are `UIStrings` entries in the DevTools frontend's `ServiceWorkersView.ts`, where each has the description `Text in Service Workers View of the Application panel` ([ServiceWorkersView.ts](https://chromium.googlesource.com/devtools/devtools-frontend/+/main/front_end/panels/application/ServiceWorkersView.ts), chromium.googlesource.com).
:::

## See also

- [ServiceWorkerRegistration: unregister() method](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/unregister) (developer.mozilla.org)
- [Service Workers](https://w3c.github.io/ServiceWorker/) (w3c.github.io)
- [CacheStorage: delete() method](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage/delete) (developer.mozilla.org)
- [skipWaiting() and the update flow](/reference/service-worker/update-skipwaiting/)
- [Service worker lifecycle](/reference/service-worker/lifecycle/)
- [Clear-Site-Data response header](/reference/storage/clearing-data/)
- [Debugging service workers](/reference/service-worker/debugging/)