Background Sync
Background Sync
Section titled “Background Sync”Most offline strategies focus on reading cached content. But what about writes — a form submission, a chat message, a purchase? If the user submits data while offline, a naive implementation either shows an error or silently drops the request. The Background Sync API solves this by letting you queue the operation and have the service worker replay it automatically once connectivity returns, even if the user has already closed the tab.
How it works
Section titled “How it works”The flow has three actors: the page, the service worker, and the browser’s sync infrastructure.
- The page detects a failed (or potentially offline) write.
- The page calls
registration.sync.register('tag-name')to schedule a sync event. - The browser fires a
syncevent on the service worker when it has a stable connection. The SW retries the operation and callsevent.waitUntil()so the browser keeps it alive until the work is done. - If the sync attempt fails, the browser retries with exponential back-off.
Browser support
Section titled “Browser support”Background Sync is currently supported only in Chromium-based browsers (Chrome, Edge, Samsung Internet, and most Android browsers). Firefox and Safari do not implement it. This makes graceful fallback essential — if the API is not available, you should attempt the network request immediately.
Feature detection
Section titled “Feature detection”async function registerSync(registration) { if ('SyncManager' in window && registration.sync) { await registration.sync.register('send-form'); console.log('Sync registered — will retry when online'); } else { // Browser does not support Background Sync; attempt immediately console.log('Background Sync not supported — sending now'); await sendQueuedData(); }}Check for both 'SyncManager' in window and registration.sync — older Chromium versions exposed SyncManager but not registration.sync.
Page side: queueing a write
Section titled “Page side: queueing a write”When the user submits a form, save the data to IndexedDB first, then register the sync tag. The write to IndexedDB is the durable record; the sync tag is just a signal to the service worker.
async function handleSubmit(formData) { // 1. Save to IndexedDB so the SW can read it later await saveToIdb('outbox', { id: Date.now(), ...formData });
// 2. Get the SW registration const registration = await navigator.serviceWorker.ready;
if (registration.sync) { // 3. Register the background sync tag await registration.sync.register('send-form'); } else { // Fallback: try the request right now await sendQueuedData(); }}Service worker side: replaying queued data
Section titled “Service worker side: replaying queued data”The service worker listens for the sync event. The event.tag property matches the string passed to registration.sync.register().
self.addEventListener('sync', (event) => { if (event.tag === 'send-form') { event.waitUntil(replayOutbox()); }});
async function replayOutbox() { const db = await openDatabase(); const tx = db.transaction('outbox', 'readwrite'); const store = tx.objectStore('outbox'); const allRequests = await getAllRecords(store);
for (const item of allRequests) { const response = await fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item), });
if (response.ok) { // Remove from outbox after successful send await deleteRecord(store, item.id); } }}If replayOutbox() throws or returns a rejected Promise, the browser treats the sync as failed and schedules a retry. Only resolve successfully once you are sure the data has been delivered.
Sequence diagram
Section titled “Sequence diagram”sequenceDiagram
participant U as User
participant P as Page
participant IDB as IndexedDB
participant SW as Service Worker
participant API as Server API
U->>P: Submits form (offline)
P->>IDB: saveToIdb('outbox', data)
P->>SW: registration.sync.register('send-form')
Note over U,SW: Connection restored
SW->>SW: sync event fires (tag: send-form)
SW->>IDB: read all outbox records
IDB-->>SW: queued items
SW->>API: POST /api/submit
API-->>SW: 200 OK
SW->>IDB: delete sent record Interactive demo
Section titled “Interactive demo”The playground below demonstrates the full flow. The page lets you submit a message. The service worker queues it to an in-memory outbox and registers a sync tag. When the browser fires the sync event the SW logs the replay. Toggle the browser’s Network panel to “Offline” before submitting to see the queue in action.