Periodic Background Sync — Updating Content on a Schedule
In this tutorial, you will learn about Periodic Background Sync. We cover key concepts, practical examples, and best practices to help you master this topic.
Periodic Background Sync updates PWA content on a schedule, keeping cached data fresh without requiring the user to open the app or even keep the browser running.
What You'll Learn
By the end of this tutorial, you will understand how Periodic Background Sync works, how to register periodic syncs, how to handle them in the service worker, and limitations across browsers.
Why It Matters
Most offline caches become stale over time. Without periodic updates, users opening an offline PWA see data that may be hours or days old. Periodic Sync solves this by silently refreshing cached content in the background, ensuring users always have fresh data even before they open the app.
Real-World Use
A news PWA registers a periodic sync for every 6 hours. When the user wakes up and opens the app offline, the cached articles are from the most recent sync (a few hours ago), not from the last visit days ago. The user always sees reasonably fresh content.
Periodic Sync Flow
Periodic Sync Flow
User visits PWA and grants permission
↓
Page registers periodic sync with tag and minInterval
↓
Browser stores the registration
↓
... (user closes app) ...
↓
Browser determines appropriate time
(considering battery, network, user engagement)
↓
Fires 'periodicsync' event in service worker
↓
Service worker fetches fresh content
↓
Updates caches with new data
↓
Waits for next period
Think of Periodic Sync like a newspaper delivery. You subscribe (register) and the paper arrives every morning (periodic refresh). You do not need to go to the store (open the app) to get fresh news. The delivery happens while you sleep.
Checking and Registering
// Check Periodic Background Sync support
function isPeriodicSyncSupported() {
return 'serviceWorker' in navigator &&
'PeriodicSyncManager' in window;
}
// Register a periodic sync
async function registerPeriodicSync() {
if (!isPeriodicSyncSupported()) {
console.log('Periodic sync not supported');
return;
}
try {
const registration = await navigator.serviceWorker.ready;
// Check existing registrations
const tags = await registration.periodicSync.getTags();
console.log('Existing periodic syncs:', tags);
// Register new sync
await registration.periodicSync.register('update-articles', {
minInterval: 6 * 60 * 60 * 1000 // 6 hours
});
console.log('Periodic sync registered: update-articles');
} catch (error) {
console.error('Periodic sync registration failed:', error);
}
}
// Unregister a periodic sync
async function unregisterPeriodicSync(tag) {
const registration = await navigator.serviceWorker.ready;
await registration.periodicSync.unregister(tag);
console.log('Unregistered periodic sync:', tag);
}
Handling Periodic Sync in the Service Worker
// sw.js — Handle periodic sync
self.addEventListener('periodicsync', event => {
console.log('Periodic sync:', event.tag);
if (event.tag === 'update-articles') {
event.waitUntil(updateArticles());
} else if (event.tag === 'update-weather') {
event.waitUntil(updateWeather());
} else if (event.tag === 'sync-offline-data') {
event.waitUntil(refreshOfflineData());
}
});
async function updateArticles() {
console.log('Periodic sync: updating articles');
try {
const response = await fetch('/api/articles?limit=20');
if (response.ok) {
const cache = await caches.open('articles-cache');
await cache.put('/api/articles', response.clone());
console.log('Articles cache updated');
}
} catch (error) {
console.log('Periodic sync: article update failed', error.message);
}
}
async function updateWeather() {
console.log('Periodic sync: updating weather');
try {
const response = await fetch('/api/weather');
if (response.ok) {
const cache = await caches.open('weather-cache');
await cache.put('/api/weather', response.clone());
console.log('Weather cache updated');
}
} catch (error) {
console.log('Periodic sync: weather update failed');
}
}
Managing Periodic Syncs
// List all registered periodic syncs
async function listPeriodicSyncs() {
try {
const registration = await navigator.serviceWorker.ready;
const tags = await registration.periodicSync.getTags();
if (tags.length === 0) {
console.log('No periodic syncs registered');
return;
}
console.log('Registered periodic syncs:');
for (const tag of tags) {
console.log(' -', tag);
}
} catch (error) {
console.error('Failed to list periodic syncs:', error);
}
}
// Unregister all periodic syncs
async function unregisterAllSyncs() {
const registration = await navigator.serviceWorker.ready;
const tags = await registration.periodicSync.getTags();
for (const tag of tags) {
await registration.periodicSync.unregister(tag);
console.log('Unregistered:', tag);
}
}
Permission for Periodic Sync
Periodic Sync requires a permission check:
// Check periodic sync permission
async function checkPeriodicSyncPermission() {
const status = await navigator.permissions.query({
name: 'periodic-background-sync'
});
console.log('Periodic sync permission:', status.state);
status.addEventListener('change', () => {
console.log('Permission changed:', status.state);
});
return status.state;
}
// Request periodic sync with permission handling
async function setupPeriodicSync() {
const permission = await checkPeriodicSyncPermission();
if (permission === 'granted') {
await registerPeriodicSync();
} else if (permission === 'prompt') {
// Browser will prompt automatically when register() is called
await registerPeriodicSync();
} else {
console.log('Periodic sync permission denied');
// Fall back to regular Background Sync or push-based updates
}
}
Periodic Sync with Network-Aware Updates
self.addEventListener('periodicsync', event => {
event.waitUntil(
// Check if we should sync based on network conditions
isGoodConnection().then(good => {
if (good) {
return performUpdate(event.tag);
}
console.log('Skipping periodic sync: poor connection');
})
);
});
async function isGoodConnection() {
if ('connection' in navigator) {
const conn = navigator.connection;
// Only sync on fast connections
return conn.effectiveType !== 'slow-2g' &&
conn.effectiveType !== '2g' &&
conn.downlink > 0.5;
}
return true; // Assume good if we cannot check
}
async function performUpdate(tag) {
const updateMap = {
'update-articles': () => fetchAndCache('/api/articles', 'articles-cache'),
'update-weather': () => fetchAndCache('/api/weather', 'weather-cache'),
'sync-offline-data': () => refreshIndexedDBData()
};
const updateFn = updateMap[tag];
if (updateFn) {
await updateFn();
console.log('Periodic update complete for:', tag);
}
}
async function fetchAndCache(url, cacheName) {
const response = await fetch(url);
if (response.ok) {
const cache = await caches.open(cacheName);
await cache.put(url, response);
}
}
Common Mistakes
- Setting minInterval too low. The browser respects battery and network conditions. A 5-minute interval may fire only once per hour. Use realistic expectations.
- Not checking permission before registering. Periodic Sync requires permission. Always check and handle the 'denied' state gracefully.
- Performing heavy work in the sync handler. Periodic syncs should be lightweight. Fetch and cache only what is necessary. Heavy operations may be throttled.
- Not handling fetch failures in periodic sync. If the network is unavailable during the sync, the cache is not updated. Handle failures silently and wait for the next cycle.
- Registering multiple syncs with overlapping purposes. Use a single periodic sync to update multiple caches rather than registering separate syncs for each.
Practice Questions
- How does Periodic Background Sync differ from regular Background Sync?
- What factors determine when the browser fires a periodic sync event?
- What permission is required for Periodic Background Sync?
- Why should you check network quality before performing a periodic sync?
- How do you unregister a specific periodic sync?
Challenge: Set up a Periodic Background Sync that updates three caches (articles, weather, notifications) every 12 hours. Check network quality before syncing (skip if 2G or slower). Log each sync attempt with timestamp and result. Test by registering, waiting for the sync, and verifying cache updates.
FAQ
Mini Project
Create a periodic sync system for a news PWA: register a periodic sync every 6 hours, fetch fresh article data from an API (limit 10 articles), update the cache, and verify the cache timestamp changes after the sync. Add network quality checking and fallback to push-triggered updates for browsers without Periodic Sync support.
What's Next
Your PWA can update itself. Now learn how to implement the install prompt — the browser dialog that lets users add your PWA to their home screen.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro