# Periodic background sync for content updates

> When periodic background sync is the right tool for refreshing content, how Chrome gates it on installation and engagement, and how to test it.

import Figure from '@components/Figure.astro';
import periodicSyncDiagram from '@assets/diagrams/periodic-background-sync.svg';

Periodic background sync lets an installed PWA refresh its cached content on a schedule the
browser chooses, by firing a `periodicsync` event in the service worker while no page is open.
It exists so that a news or weather app opens with current data instead of a spinner, and it is
Chromium-only, gated on installation and on how often the user actually opens the app.

<Figure src={periodicSyncDiagram} alt="Flow diagram of periodic background sync: an installed PWA with enough site engagement checks the periodic-background-sync permission and calls periodicSync.register() with a tag and minInterval; the browser schedules the sync no more often than minInterval, fires the periodicsync event in the service worker when the time arrives and the device is online, and the worker fetches and caches new data inside event.waitUntil(). The cycle repeats until the app is uninstalled, engagement decays or periodicSync.unregister() is called." caption="Periodic background sync: from permission and registration to the repeating periodicsync event." />

## How it works

The page registers a tag with `registration.periodicSync.register(tag, { minInterval })`; the
browser then fires `periodicsync` in the service worker no more often than `minInterval`, when
the device is online, and only for as long as it judges the app worth waking. The API surface
(`PeriodicSyncManager`, `register()`, `getTags()`, `unregister()`, the `periodicsync` event) is in
Chrome 80 and Edge 80 and nowhere else (BCD `api.PeriodicSyncManager`); the method-level reference
is the [service worker entry](/reference/service-worker/periodic-background-sync/).

### Chrome's gates

Chrome fires the event only for a web app the user has installed and launched as a standalone
app, not for a site in a tab, and only when the device is on a network it has used before
([Periodic background sync](https://developer.chrome.com/docs/capabilities/periodic-background-sync),
developer.chrome.com). The frequency is tied to the site engagement score Chrome keeps per origin,
visible at `about://site-engagement/`: a score of zero means no events at all, and a higher score
allows syncs closer to `minInterval`. If the user stops opening the app, the score decays and
the events stop. `minInterval` is therefore a floor rather than a schedule, and a registration with
`minInterval: 0` leaves the cadence entirely to Chrome.

### Permission

The feature is gated by the `periodic-background-sync` permission, which Chrome grants
automatically to installed apps without a prompt. `navigator.permissions.query({ name:
'periodic-background-sync' })` reports `granted` or `denied` from a window or a service worker,
and `register()` rejects with `NotAllowedError` when it is denied, `InvalidStateError` when the
registration has no active worker, and `InvalidAccessError` when called from a window that is not
a top-level browsing context ([PeriodicSyncManager: register()](https://developer.mozilla.org/en-US/docs/Web/API/PeriodicSyncManager/register),
developer.mozilla.org).

### What it is for, and what it is not

Background sync, despite the similar name, retries a failed request once connectivity returns;
periodic background sync pulls fresh data on a cadence. Web Push also wakes the worker but
interrupts the user with a notification and must do so under `userVisibleOnly`. Periodic sync is
the only quiet, repeating option, which makes it right for a daily article cache and wrong for
anything that must happen at a particular time; for that there is no shipped API, as the
[notification triggers](/reference/notifications/notification-triggers/) entry explains.

## Examples

Both examples keep the page-load refresh as the fallback and let the API remove it where it works.

### Registering a daily article refresh when the app can

Register after the service worker is active and the permission is granted. The `catch` covers
the tab case in Chrome and every other engine, where the code falls back to refreshing on load.

```js
async function enableDailyRefresh() {
  const registration = await navigator.serviceWorker.ready;
  if (!('periodicSync' in registration)) {
    return refreshOnLoad(); // Firefox, Safari, or an uninstalled Chrome tab
  }
  const status = await navigator.permissions.query({ name: 'periodic-background-sync' });
  if (status.state !== 'granted') return refreshOnLoad();
  try {
    await registration.periodicSync.register('update-articles', { minInterval: 24 * 60 * 60 * 1000 });
  } catch {
    refreshOnLoad(); // permission denied or no active worker: behave as before
  }
}
```

`refreshOnLoad()` is the pre-existing behaviour, so an engine without the API loses nothing; the
API removes the spinner where it is available.

### Handling the event and skipping the load-time refresh

The worker caches the response inside `event.waitUntil()`; the page checks `getTags()` and skips
its own refresh when the sync is registered, because the cache is already fresh.

```js
// service worker
self.addEventListener('periodicsync', (event) => {
  if (event.tag === 'update-articles') {
    event.waitUntil(caches.open('articles').then((cache) => cache.add('/api/articles')));
  }
});

// page
async function maybeRefresh(registration) {
  const tags = 'periodicSync' in registration ? await registration.periodicSync.getTags() : [];
  if (!tags.includes('update-articles')) refreshOnLoad();
}
```

Keep the handler small: Chrome gives the worker a limited window, and a sync that downloads
megabytes on a metered connection is the behaviour that lowers the engagement budget.

:::observed
Chrome DevTools, Application > Background services > Periodic background sync, has a **Start
recording** button; once pressed, recording stays on for up to three days and lists each
`register`, `periodicsync` dispatch, and `unregister` with its tag and timestamp, which is the
only way to see when Chrome actually chose to fire the event ([Periodic background
sync](https://developer.chrome.com/docs/capabilities/periodic-background-sync),
developer.chrome.com). The same guide points to `about://site-engagement/`, where each origin's
score is listed; an installed app with a score of `0` registers successfully and receives no
events.
:::

## See also

- [Web Periodic Background Synchronization](https://wicg.github.io/periodic-background-sync/) (wicg.github.io)
- [Periodic background sync](https://developer.chrome.com/docs/capabilities/periodic-background-sync) (developer.chrome.com)
- [PeriodicSyncManager.register()](/reference/service-worker/periodic-background-sync/)
- [Background sync](/reference/service-worker/background-sync/)
- [Web Push and PushManager.subscribe()](/reference/notifications/web-push/)
- [Notification triggers (showTrigger)](/reference/notifications/notification-triggers/)