How Clients.claim() Works in Service Workers
This article explains how the Clients.claim() method
allows an activated JavaScript service worker to immediately control
existing open pages within its scope. By default, a newly activated
service worker does not control open tabs until they are reloaded;
Clients.claim() overrides this behavior, enabling instant
interception of network requests and immediate client management.
The Default Service Worker Lifecycle
When a service worker is registered and activated, it does not immediately take control of the page that loaded it or other tabs that are already open. Under standard lifecycle rules:
- A page must be refreshed or a new tab must be navigated to the worker’s scope before the new service worker begins controlling it.
- If an older service worker is running, it continues to handle requests for open tabs until those tabs are completely closed or reloaded.
This default design ensures consistent behavior across a browsing session, preventing pages from suddenly receiving different responses from a newly updated service worker mid-session.
How
Clients.claim() Changes Control
The self.clients.claim() method is called from within
the service worker context—typically during the activate
event. When invoked, it queries all open browser contexts (tabs,
windows, iframes) that match the service worker’s scope and assigns the
active service worker as their controller.
self.addEventListener('activate', (event) => {
event.waitUntil(clients.claim());
});When this execution occurs:
- Immediate Interception: Any network request made by
currently open pages after
claim()resolves will pass through the service worker’sfetchevent handlers without requiring a page refresh. - Client Synchronization: The
navigator.serviceWorker.controllerproperty on open pages immediately switches to reference the new service worker instance. controllerchangeEvent Dispatch: Open client pages receive acontrollerchangeevent on thenavigator.serviceWorkerinterface, allowing client-side scripts to react, update the UI, or reload state if necessary.
// In the client page script
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('The controlling service worker has changed.');
});Combining
skipWaiting() and clients.claim()
Clients.claim() is frequently paired with
self.skipWaiting() to achieve rapid deployment of new
service worker logic across an application:
self.skipWaiting()runs in theinstallphase, forcing the new service worker to bypass the waiting state and activate immediately.self.clients.claim()runs in theactivatephase, forcing the newly active worker to immediately seize control of all active clients.
Together, these methods allow progressive web apps (PWAs) to apply critical updates, establish offline handling on initial site visits, and push functional changes to users instantly.