Skip to content

Periodic Background Sync — Updating Content on a Schedule

DodaTech Updated 2026-06-28 6 min read

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

  1. Setting minInterval too low. The browser respects battery and network conditions. A 5-minute interval may fire only once per hour. Use realistic expectations.
  2. Not checking permission before registering. Periodic Sync requires permission. Always check and handle the 'denied' state gracefully.
  3. Performing heavy work in the sync handler. Periodic syncs should be lightweight. Fetch and cache only what is necessary. Heavy operations may be throttled.
  4. 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.
  5. Registering multiple syncs with overlapping purposes. Use a single periodic sync to update multiple caches rather than registering separate syncs for each.

Practice Questions

  1. How does Periodic Background Sync differ from regular Background Sync?
  2. What factors determine when the browser fires a periodic sync event?
  3. What permission is required for Periodic Background Sync?
  4. Why should you check network quality before performing a periodic sync?
  5. 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

Does Periodic Background Sync work on iOS?

No. Safari does not support Periodic Background Sync. Use push notifications to trigger cache updates on iOS.

How frequently can periodic sync fire?

The minimum interval is not a guarantee. Chrome may delay syncs based on user engagement, battery level, and network conditions. Expect intervals of 6-24 hours in practice.

Does periodic sync work when the browser is closed?

On Android, yes. On desktop, the browser must have been running recently. The sync service has limits on background time.

Can I have multiple periodic syncs?

Yes, up to a browser-determined limit. Each sync needs a unique tag. Chrome allows approximately 5-10 periodic sync registrations per origin.

What happens if the periodic sync handler throws an error?

The error is caught silently. The browser retries on the next scheduled interval. Unhandled errors do not affect other syncs.

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