# Background sync, periodic sync, and Web Locks

> Choose one-off background sync, periodic background sync, or Web Locks by trigger, lifetime, support, and failure behavior.

Background Sync asks a service worker to retry deferred work when the user agent considers the network suitable. Periodic Background Sync asks a service worker to refresh data at browser-selected intervals, while Web Locks coordinates live tabs and workers that contend for an application-defined resource.

The support split changes the fallback. BCD `api.SyncManager` records Background Sync from Chrome 49 and mirrors that support for Edge, with no Firefox or Safari support. BCD `api.PeriodicSyncManager` records periodic sync from Chrome 80 and mirrors that support for Edge, with no Firefox or Safari support. BCD `api.LockManager` records Web Locks from Chrome 69, mirrors that support for Edge, and records support from Firefox 96 and Safari 15.4.

## How it works

Each mechanism answers a different scheduling question. Background Sync registers a tag through `ServiceWorkerRegistration.sync`; the user agent later delivers a `sync` event to the active service worker. Periodic Background Sync registers a tag and `minInterval` through `ServiceWorkerRegistration.periodicSync`; the specification leaves the actual firing time to the user agent. Web Locks calls `navigator.locks.request()` and runs a callback after the named lock is granted. The lock lasts until that callback's promise settles.

| Mechanism | Trigger | Execution context | Useful fallback |
|---|---|---|---|
| Background Sync | The user agent finds network conditions suitable after `sync.register()` | Service worker `sync` event | Retry from the next page load |
| Periodic Background Sync | The user agent chooses a time no more frequent than the registered `minInterval` | Service worker `periodicsync` event | Refresh when a page opens |
| Web Locks | A named lock becomes available after `navigator.locks.request()` | The requesting window or worker | Run without cross-context coordination or use an application-level lease |

Background Sync fits an operation that has already failed or was deliberately deferred, such as sending a queued form. The Background Sync specification defines one-off registration through `SyncManager`. A separate Periodic Background Sync specification defines periodic registration through `PeriodicSyncManager`. Registering work does not make the request itself durable: store the request data before registering the tag, then remove it after the service worker completes the operation.

Periodic Background Sync fits data that becomes stale without a user action. Chrome's guide says synchronization timing is not controlled by the developer and considers engagement, previously used networks, power, and connectivity. `minInterval` is a lower bound, not a timer.

Web Locks does not schedule a service worker or wait for connectivity. The Web Locks specification grants exclusive locks one at a time and allows multiple shared locks on one name. The callback controls the lifetime, so use it around the smallest asynchronous operation that must not overlap. A closed page cannot use a Web Lock as a durable retry queue.

| Decision | Choose | Reason |
|---|---|---|
| Retry a stored mutation after connectivity returns | Background Sync | The `sync` event is designed for deferred one-off work |
| Refresh cached content without an open page | Periodic Background Sync | The `periodicsync` event is designed for browser-scheduled refreshes |
| Prevent two open contexts from updating the same record together | Web Locks | An exclusive lock serializes callbacks that request the same name |
| Support Firefox and Safari with the same coordination primitive | Web Locks | Their recorded support includes `LockManager`, not either sync manager |
| Require an exact wall-clock deadline | None of these | The sync specifications leave delivery to the user agent, and a lock only governs contention |

:::caution
The Periodic Background Sync specification makes `minInterval` a minimum interval and allows the user agent to delay events. Chrome's [Periodic Background Sync guide](https://developer.chrome.com/docs/capabilities/periodic-background-sync) adds engagement, power, connectivity, and previously used network considerations. Do not label a `minInterval` value as the next execution time.

The same specification requires `PeriodicSyncManager.register()` to reject with `InvalidStateError` when the service worker registration has no active worker and with `NotAllowedError` when periodic background sync permission is not granted. Handle those named failures through the page-load refresh path.
:::

## Examples

Store durable work first, and keep runtime coordination separate from delivery scheduling. Both examples include a page-load path so engines without a sync API still make progress.

### Retrying a queued request without Background Sync

The page writes the operation to IndexedDB before asking for a one-off sync. If `SyncManager` is absent or registration rejects, it calls the same queue processor while the page remains open.

```js
async function queueMessage(message) {
  await outbox.put(message);
  const registration = await navigator.serviceWorker.ready;
  if (!('sync' in registration)) {
    return flushOutbox();
  }
  try {
    await registration.sync.register('send-outbox');
  } catch (error) {
    await flushOutbox();
    reportRetryFallback(error);
  }
}
```

The service worker handles the same stored queue. Passing the promise to `waitUntil()` lets the user agent extend the event while `flushOutbox()` runs, as required by the Background Sync event model.

```js
self.addEventListener('sync', (event) => {
  if (event.tag !== 'send-outbox') return;
  event.waitUntil(flushOutbox());
});

self.addEventListener('message', (event) => {
  if (event.data?.type !== 'flush-outbox') return;
  event.waitUntil(flushOutbox());
});
```

### Refreshing once while periodic sync is unavailable

The page registers periodic refresh only after the API and permission checks pass. The failure path refreshes immediately and leaves the next page load responsible for another attempt.

```js
async function scheduleCatalogRefresh() {
  const registration = await navigator.serviceWorker.ready;
  if (!('periodicSync' in registration)) {
    return refreshCatalogNow();
  }
  const status = await navigator.permissions.query({
    name: 'periodic-background-sync',
  });
  if (status.state !== 'granted') return refreshCatalogNow();
  try {
    await registration.periodicSync.register('catalog', {
      minInterval: 24 * 60 * 60 * 1000,
    });
  } catch (error) {
    await refreshCatalogNow();
    reportRefreshFallback(error);
  }
}
```

When several open contexts can call `refreshCatalogNow()`, wrap that function with a Web Lock. This changes overlap between live contexts; it does not replace the stored queue or cause a service worker event.

```js
async function refreshCatalogOnce() {
  if (!('locks' in navigator)) return refreshCatalogNow();
  return navigator.locks.request('catalog-refresh', {
    ifAvailable: true,
  }, async (lock) => {
    if (!lock) return { status: 'already-running' };
    await refreshCatalogNow();
    return { status: 'refreshed' };
  });
}
```

The [Background Sync compatibility data](/compatibility/background-sync/) and [Web Locks compatibility data](/reference/capabilities/web-locks/) show why both feature tests remain necessary.

## See also

- [Background Synchronization](https://wicg.github.io/background-sync/spec/) (wicg.github.io)
- [Web Periodic Background Synchronization](https://wicg.github.io/periodic-background-sync/) (wicg.github.io)
- [Web Locks API](https://w3c.github.io/web-locks/) (w3c.github.io)
- [Background Sync](/reference/service-worker/background-sync/)
- [Periodic Background Sync](/reference/service-worker/periodic-background-sync/)
- [Web Locks API](/reference/capabilities/web-locks/)