· deepdives  · 6 min read

Mastering Periodic Background Sync: A Step-by-Step Guide

A comprehensive tutorial that walks through implementing the Periodic Background Sync API in service workers, covers browser compatibility, testing strategies, real-world use cases, and best practices for battery and privacy-aware background updates.

A comprehensive tutorial that walks through implementing the Periodic Background Sync API in service workers, covers browser compatibility, testing strategies, real-world use cases, and best practices for battery and privacy-aware background updates.

Why Periodic Background Sync matters

Progressive Web Apps (PWAs) often need to keep content fresh even when users are intermittently online. The Periodic Background Sync API gives websites a way to request recurring background work from the browser so your service worker can fetch updates, refresh caches, and prepare offline content without a foreground tab being open.

This guide covers how to implement Periodic Background Sync, what permissions and capabilities are required, browser compatibility, practical examples, and recommended patterns to use it responsibly.

Quick high-level flow

  1. Register a service worker in your page.
  2. Ask the Permissions API for periodic-background-sync (or check feature availability).
  3. Register a periodic sync with registration.periodicSync.register(tag, { minInterval }).
  4. Handle periodicsync events inside your service worker and perform the background work.
  5. Unregister when not needed and handle graceful fallbacks.

Feature detection and permissions

Always detect API availability and query permissions before attempting registration.

// In the main thread
async function supportsPeriodicSync() {
  if (!('serviceWorker' in navigator) || !('PeriodicSyncManager' in window)) {
    return false;
  }
  const registration = await navigator.serviceWorker.ready;
  return 'periodicSync' in registration; // modern check
}

// Check permission
async function checkPeriodicSyncPermission() {
  if (!('permissions' in navigator)) return 'unsupported';
  try {
    const status = await navigator.permissions.query({
      name: 'periodic-background-sync',
    });
    return status.state; // 'granted' | 'prompt' | 'denied'
  } catch (err) {
    // Some browsers may throw - treat as unsupported
    return 'unsupported';
  }
}

Notes:

  • The Permissions API may not be implemented identically across browsers; always fallback gracefully.
  • Even with granted, the browser applies heuristics (battery, data saver, usage patterns) - it may throttle or delay work.

Registering a periodic sync (step-by-step)

  1. Register a service worker in your page (if not already done).
// register-sw.js (main thread)
if ('serviceWorker' in navigator) {
  navigator.serviceWorker
    .register('/sw.js')
    .then(() => console.log('Service Worker registered'))
    .catch(err => console.error('SW registration failed', err));
}
  1. Request permission and register the periodic sync. The minInterval is the minimum allowed interval you ask for; the browser may schedule less or more frequently.
// register-periodic.js (main thread)
async function registerDailySync() {
  if (!(await supportsPeriodicSync())) {
    console.warn('Periodic Background Sync not supported');
    return;
  }

  const perm = await checkPeriodicSyncPermission();
  if (perm === 'denied') return; // respect user

  try {
    const registration = await navigator.serviceWorker.ready;
    // Ask for a daily sync (24 hours in ms)
    await registration.periodicSync.register('refresh-news', {
      minInterval: 24 * 60 * 60 * 1000, // milliseconds
    });
    console.log('Registered periodic sync: refresh-news');
  } catch (err) {
    console.error('Periodic sync registration failed:', err);
  }
}

registerDailySync();
  1. Handle the periodicsync event inside your service worker.
// sw.js (service worker)
self.addEventListener('periodicsync', event => {
  if (event.tag === 'refresh-news') {
    event.waitUntil(handleRefreshNews());
  }
});

async function handleRefreshNews() {
  // Respect data-saver and connectivity if appropriate
  const clientHints = self.registration.scope ? {} : {};

  try {
    const response = await fetch('/api/latest-news');
    if (!response || !response.ok) throw new Error('Network error');

    const articles = await response.json();

    // Update IndexedDB or Cache Storage for offline reads
    const cache = await caches.open('news-cache-v1');
    await cache.put(
      '/cached-latest-news.json',
      new Response(JSON.stringify(articles))
    );

    // Optionally show a notification to the user
    self.registration.showNotification('News updated', {
      body: 'Latest articles are ready for offline reading.',
      tag: 'news-update',
    });
  } catch (err) {
    console.error('Periodic sync failed:', err);
  }
}

Important: Always wrap asynchronous work with event.waitUntil() so the browser knows the work is ongoing and can keep the service worker alive until it completes.

Handling edge cases and graceful fallback

  • If Periodic Background Sync is unavailable, fall back to other strategies:

    • One-off Background Sync (SyncManager / sync event) to retry failed uploads when connectivity returns.
    • Use push notifications (server-initiated) to prompt a foreground refresh.
    • Rely on regular foreground checks when the app is opened.
  • Respect Save-Data and battery saver settings. Use the Network Information API (navigator.connection.saveData or navigator.connection.effectiveType) to reduce frequency or payload size.

  • Let users opt-in/out of background updates in your app settings.

Browser compatibility and current landscape

Periodic Background Sync is still an evolving feature. As of the last updates:

  • Chromium-based browsers (Chrome on Android) have had experimental support and evolving implementations behind flags or origin trials. The browser applies heuristics (battery, user engagement, data) before granting regular background execution.
  • Firefox and Safari (including iOS) do not support Periodic Background Sync.
  • Because support is limited and behavior is heuristic-driven, always implement progressive enhancement and alternatives.

References and status pages:

Note: the landscape changes. Check current browser-release notes and Chromium status for up-to-date info.

Real-world use cases and patterns

  • News and content apps: prefetch top stories nightly so users can read offline.
  • Transit or map apps: refresh cached schedules or map tiles before commuting hours.
  • Offline-first apps: keep critical data (product catalog, docs) up-to-date for offline use.
  • Analytics batching: upload batched analytics when network conditions are favorable (consider privacy and user consent).
  • IoT dashboards: synchronize device state caches for faster UI updates when users open the site.

For each use case, consider:

  • Why background updates are needed (UX improvement vs. essential functionality).
  • Frequency and data budget.
  • User control (allow opt-in, show status, allow manual refresh).

Quotas, heuristics, and expectations

Browsers will not guarantee exact timing. They consider:

  • User engagement with your origin (how often the site is used).
  • Device battery level and power-saving modes.
  • Data-saver preferences.
  • System-level scheduling and other sites’ demand.

Expect that periodic sync may be delayed or batched. Design your app to make progress gracefully even if the sync fires less frequently than requested.

Testing and debugging tips

  • In Chromium-based browsers, you may need experimental flags enabled or an origin trial for older versions. Use chrome://flags carefully.
  • Use DevTools > Application > Service Workers to inspect registrations.
  • Use registration.periodicSync.getTags() in the console to list registered tags.

Example console check:

navigator.serviceWorker.ready.then(async reg => {
  if ('periodicSync' in reg) {
    const tags = await reg.periodicSync.getTags();
    console.log('Periodic sync tags:', tags);
  }
});
  • To simulate offline behavior, use DevTools Network throttling and test service worker fetch handlers.
  • Log extensively in your service worker and check chrome://serviceworker-internals or DevTools’ console for service worker logs.

Privacy, security, and battery considerations

  • Ask only for the permission you need; explain why. Be transparent about what background operations do and how they affect data usage.
  • Prefer small, incremental updates rather than heavy downloads.
  • Respect the user’s save-data preference and allow disabling background updates in-app.
  • Avoid using Periodic Background Sync for background spying or any activity that could be considered surprising to the user.

Unregistering and lifecycle management

When a user disables background updates, or when you want to change behavior:

// Unregister a tag
async function unregisterTag(tag) {
  const registration = await navigator.serviceWorker.ready;
  if ('periodicSync' in registration) {
    await registration.periodicSync.unregister(tag);
    console.log('Unregistered', tag);
  }
}

// List tags
async function listPeriodicTags() {
  const registration = await navigator.serviceWorker.ready;
  if ('periodicSync' in registration) {
    return await registration.periodicSync.getTags();
  }
  return [];
}

Putting it all together - a compact example

  • Main thread: register service worker and periodic sync when user opts in.
  • Service worker: fetch and cache content, then optionally show a notification.

Project structure:

  • /index.html
  • /main.js (registers SW + periodic sync)
  • /sw.js (handles periodicsync)

This is already demonstrated in the code snippets above.

Best practices checklist

  • Feature-detect and fall back gracefully.
  • Use the Permissions API to respect user intent.
  • Choose conservative minInterval values; avoid frequent heavy downloads.
  • Honor Save-Data and battery saver settings.
  • Provide clear in-app controls for enabling/disabling background updates.
  • Test across devices and browsers; expect different behavior depending on heuristics.

By following these steps and principles, you can add recurring, low-impact background updates to your web app that improve perceived performance and offline readiness while respecting users’ device resources and privacy.

Back to Blog

Related Posts

View All Posts »