Registers service worker. Returns Promise<ServiceWorkerRegistration>. Requires HTTPS.
All Modern Browsers
getRegistration
navigator.serviceWorker.getRegistration(scope)
Gets existing registration for scope. Returns Promise<ServiceWorkerRegistration>.
All Modern Browsers
getRegistrations
navigator.serviceWorker.getRegistrations()
Gets all registrations. Returns Promise<ServiceWorkerRegistration[]>.
All Modern Browsers
Lifecycle Event
When Fired
Use Case
install
Service worker first installed. Runs only once per version.
Cache static assets, setup
activate
Service worker activated. After install or on page reload for updated worker.
Clean old caches, claim clients
fetch
Network request intercepted. Fires for all page requests.
Cache strategies, offline support
message
Message received from client (page/worker).
Client-worker communication
sync
Background sync triggered (when online).
Retry failed requests
push
Push notification received.
Show notifications
ServiceWorkerRegistration Property
Type
Description
installing
ServiceWorker
Service worker currently installing. null if none.
waiting
ServiceWorker
Service worker installed, waiting to activate. null if none.
active
ServiceWorker
Active service worker controlling pages. null if none.
scope
string
URL scope of service worker (e.g., "/app/").
updateViaCache
string
Cache mode for updates: "imports", "all", "none".
Example: Register service worker
// Check supportif ("serviceWorker" in navigator) { console.log("Service Workers supported");} else { console.log("Service Workers not supported");}// Register service workerasync function registerServiceWorker() { try { const registration = await navigator.serviceWorker.register("/sw.js", { "scope": "/" // Default is script location }); console.log("Service Worker registered:", registration.scope); // Check state if (registration.installing) { console.log("Service Worker installing"); } else if (registration.waiting) { console.log("Service Worker waiting"); } else if (registration.active) { console.log("Service Worker active"); } return registration; } catch (error) { console.error("Service Worker registration failed:", error); }}// Register on page loadwindow.addEventListener("load", () => { registerServiceWorker();});// Listen for updatesnavigator.serviceWorker.addEventListener("controllerchange", () => { console.log("Service Worker controller changed"); // New service worker took control window.location.reload();});// Check for updates manuallyasync function checkForUpdates() { const registration = await navigator.serviceWorker.getRegistration(); if (registration) { await registration.update(); console.log("Checked for updates"); }}// Unregister service workerasync function unregisterServiceWorker() { const registration = await navigator.serviceWorker.getRegistration(); if (registration) { const success = await registration.unregister(); console.log("Unregistered:", success); }}
Example: Service worker lifecycle (sw.js)
const CACHE_NAME = "my-app-v1";const STATIC_ASSETS = [ "/", "/index.html", "/styles.css", "/app.js", "/logo.png"];// Install event - cache static assetsself.addEventListener("install", (event) => { console.log("Service Worker installing"); event.waitUntil( caches.open(CACHE_NAME).then((cache) => { console.log("Caching static assets"); return cache.addAll(STATIC_ASSETS); }).then(() => { // Skip waiting to activate immediately return self.skipWaiting(); }) );});// Activate event - clean old cachesself.addEventListener("activate", (event) => { console.log("Service Worker activating"); event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((name) => name !== CACHE_NAME) .map((name) => { console.log("Deleting old cache:", name); return caches.delete(name); }) ); }).then(() => { // Take control of all clients immediately return self.clients.claim(); }) );});// Fetch event - serve from cache or networkself.addEventListener("fetch", (event) => { event.respondWith( caches.match(event.request).then((response) => { // Cache hit - return cached response if (response) { return response; } // Cache miss - fetch from network return fetch(event.request).then((response) => { // Don't cache non-GET requests or non-ok responses if (event.request.method !== "GET" || !response.ok) { return response; } // Clone response (can only read once) const responseToCache = response.clone(); // Cache for next time caches.open(CACHE_NAME).then((cache) => { cache.put(event.request, responseToCache); }); return response; }); }).catch(() => { // Network failed - return offline page return caches.match("/offline.html"); }) );});
Note: Service Workers require HTTPS (localhost OK for development). Only one service worker active per scope. Use skipWaiting() to activate immediately, clients.claim() to control existing pages. Updates check every 24 hours or on navigation. Use versioned cache names to manage updates.
Warning: Service worker has no DOM access - can't manipulate page directly. Runs in separate thread. Use event.waitUntil() to extend event lifetime for async operations. Service worker can be terminated anytime - don't rely on global state. Test offline behavior thoroughly.
2. Service Worker Message Passing and Communication
Gets all client windows/tabs. Options: includeUncontrolled, type.
Broadcast to all tabs
clients.get(id)
Gets specific client by ID. Returns Promise<Client>.
Reply to specific tab
clients.openWindow(url)
Opens new window/tab. Returns Promise<WindowClient>. Requires user interaction.
Open notification click
clients.claim()
Makes service worker control all clients immediately (without reload).
Take control on activate
Example: Page to service worker communication
// From page to service workerif (navigator.serviceWorker.controller) { // Send message navigator.serviceWorker.controller.postMessage({ "type": "CACHE_URLS", "urls": ["/page1.html", "/page2.html"] }); console.log("Message sent to service worker");} else { console.log("No active service worker");}// Listen for messages from service workernavigator.serviceWorker.addEventListener("message", (event) => { console.log("Message from service worker:", event.data); if (event.data.type === "CACHE_UPDATED") { console.log("Cache updated:", event.data.urls); } else if (event.data.type === "NEW_VERSION") { showUpdatePrompt(); }});// Request-response pattern with message channelfunction sendMessageWithResponse(message) { return new Promise((resolve, reject) => { const messageChannel = new MessageChannel(); messageChannel.port1.onmessage = (event) => { if (event.data.error) { reject(event.data.error); } else { resolve(event.data); } }; navigator.serviceWorker.controller.postMessage( message, [messageChannel.port2] ); });}// Usageasync function getCacheInfo() { try { const response = await sendMessageWithResponse({ "type": "GET_CACHE_INFO" }); console.log("Cache info:", response); } catch (error) { console.error("Error:", error); }}
Example: Service worker to page communication
// In service worker (sw.js)// Listen for messages from pagesself.addEventListener("message", (event) => { console.log("Message received:", event.data); if (event.data.type === "CACHE_URLS") { // Cache requested URLs cacheUrls(event.data.urls).then(() => { // Reply to sender event.source.postMessage({ "type": "CACHE_UPDATED", "urls": event.data.urls }); }); } else if (event.data.type === "GET_CACHE_INFO") { // Handle request-response with message channel caches.keys().then((cacheNames) => { event.ports[0].postMessage({ "caches": cacheNames, "count": cacheNames.length }); }); } else if (event.data.type === "SKIP_WAITING") { self.skipWaiting(); }});// Broadcast to all clientsasync function notifyAllClients(message) { const clients = await self.clients.matchAll({ "includeUncontrolled": true, "type": "window" }); clients.forEach((client) => { client.postMessage(message); });}// Notify when cache updatedasync function cacheUrls(urls) { const cache = await caches.open(CACHE_NAME); await cache.addAll(urls); // Notify all tabs await notifyAllClients({ "type": "CACHE_UPDATED", "urls": urls });}// Notify specific clientasync function notifyClient(clientId, message) { const client = await self.clients.get(clientId); if (client) { client.postMessage(message); }}// Open window on notification clickself.addEventListener("notificationclick", (event) => { event.notification.close(); event.waitUntil( clients.openWindow("/notifications") );});
Note:postMessage only works if service worker is active and controlling page. Check navigator.serviceWorker.controller before sending. Messages are structured clones - can't send functions, DOM nodes. Use MessageChannel for request-response patterns.
3. Background Sync and Periodic Sync APIs
API
Method
Description
Browser Support
Background Sync
registration.sync.register(tag)
Registers one-time sync when online. Returns Promise.
Chrome, Edge
Periodic Sync
registration.periodicSync.register(tag, options)
Registers periodic sync. Options: minInterval in ms. Requires installed PWA.
Note: Background Sync has limited browser support (Chrome, Edge). Periodic Sync requires installed PWA and permission. Browser controls actual sync timing - minInterval is minimum, not guaranteed. Sync events retry automatically if promise rejects.
Warning: Don't rely on Background Sync for critical operations - not supported everywhere. Periodic Sync can be delayed/skipped by browser to save battery. Always provide fallback (manual refresh button). Test offline scenarios thoroughly.
4. Push API and Push Notifications
Method
Description
Browser Support
registration.pushManager.subscribe(options)
Subscribes to push notifications. Returns Promise<PushSubscription>. Requires permission.
Subscription expiration timestamp. null if no expiration.
Example: Subscribe to push notifications
// Request notification permissionasync function requestNotificationPermission() { const permission = await Notification.requestPermission(); console.log("Notification permission:", permission); return permission === "granted";}// Subscribe to pushasync function subscribeToPush() { try { // Check permission if (Notification.permission !== "granted") { const granted = await requestNotificationPermission(); if (!granted) { console.log("Notification permission denied"); return; } } // Get service worker registration const registration = await navigator.serviceWorker.ready; // Check existing subscription let subscription = await registration.pushManager.getSubscription(); if (!subscription) { // Subscribe (need VAPID public key from server) subscription = await registration.pushManager.subscribe({ "userVisibleOnly": true, "applicationServerKey": urlBase64ToUint8Array(VAPID_PUBLIC_KEY) }); console.log("Subscribed to push"); } else { console.log("Already subscribed"); } // Send subscription to server await sendSubscriptionToServer(subscription); return subscription; } catch (error) { console.error("Push subscription failed:", error); }}// Convert VAPID keyfunction urlBase64ToUint8Array(base64String) { const padding = "=".repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/-/g, "+") .replace(/_/g, "/"); const rawData = atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray;}// Send subscription to serverasync function sendSubscriptionToServer(subscription) { const response = await fetch("/api/subscribe", { "method": "POST", "headers": { "Content-Type": "application/json" }, "body": JSON.stringify(subscription) }); if (response.ok) { console.log("Subscription saved on server"); }}// Unsubscribeasync function unsubscribeFromPush() { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); if (subscription) { await subscription.unsubscribe(); console.log("Unsubscribed from push"); // Notify server await fetch("/api/unsubscribe", { "method": "POST", "body": JSON.stringify({ "endpoint": subscription.endpoint }) }); }}
Example: Handle push events in service worker
// In service worker - listen for push eventsself.addEventListener("push", (event) => { console.log("Push received"); let data = { "title": "New Notification", "body": "You have a new message", "icon": "/icon.png", "badge": "/badge.png" }; // Parse push data if (event.data) { data = event.data.json(); } // Show notification event.waitUntil( self.registration.showNotification(data.title, { "body": data.body, "icon": data.icon, "badge": data.badge, "data": data.url, "tag": data.tag || "default", "requireInteraction": false, "actions": [ { "action": "open", "title": "Open" }, { "action": "close", "title": "Close" } ] }) );});// Handle notification clickself.addEventListener("notificationclick", (event) => { console.log("Notification clicked:", event.action); event.notification.close(); if (event.action === "open") { // Open URL from notification data event.waitUntil( clients.openWindow(event.notification.data || "/") ); } else if (event.action === "close") { // Just close (default behavior) } else { // Default click (no action button) event.waitUntil( clients.openWindow(event.notification.data || "/") ); }});// Handle notification closeself.addEventListener("notificationclose", (event) => { console.log("Notification closed:", event.notification.tag); // Track analytics fetch("/api/analytics/notification-closed", { "method": "POST", "body": JSON.stringify({ "tag": event.notification.tag, "timestamp": Date.now() }) });});
Note: Push API requires HTTPS and service worker. Need VAPID keys from server for applicationServerKey. Must show notification when push received (userVisibleOnly: true). Subscription can expire - check expirationTime and resubscribe.
Warning: Always request permission with user gesture (button click). Don't spam notifications - users will block. Test notification display on different platforms - varies by OS. Handle subscription expiration and errors gracefully. Safari has different push implementation (APNs).
Note: Background Fetch has very limited support (Chrome only). Good for large file downloads that continue even if page closed. Browser shows download UI to user. Files available after download completes via backgroundfetchsuccess event.
6. Workbox Integration Patterns
Workbox Strategy
Description
Use Case
NetworkFirst
Network request first, fallback to cache if offline.
Dynamic content, API responses
CacheFirst
Cache first, fallback to network if missing.
Static assets, fonts, images
StaleWhileRevalidate
Return cache immediately, update cache in background.
Note: Workbox simplifies service worker development with built-in strategies and plugins. Use workbox-cli to generate precache manifest. Workbox handles common patterns: caching, routing, expiration, background sync. Consider bundle size when using Workbox - can use module imports instead of CDN.
Service Worker Best Practices
Always use versioned cache names - easier to manage updates and cleanup
Use skipWaiting() and clients.claim() for immediate activation
Implement proper fetch strategies - CacheFirst for static, NetworkFirst for dynamic
Always include offline fallback page in precache
Test service worker updates - old workers can persist and cause bugs
Use event.waitUntil() to extend event lifetime for async operations