# Background sync: retrying failed requests

> How the Background Sync API defers a failed request until the device regains connectivity, its registration/event API, and execution-time limits.

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

**In one line:** Background sync lets a web app's service worker defer an action —
such as sending a queued message — until the browser reports that the device has
connectivity again, instead of failing it outright when the network is unavailable.

<Figure src={backgroundSyncDiagram} alt="Flow diagram of background sync: a request fails offline, the page queues it in IndexedDB and calls sync.register('outbox'); the browser holds the tag until connectivity returns, then fires the sync event in the service worker, which replays the queue inside event.waitUntil(). A resolved promise clears the registration, a rejection with lastChance false is retried later with backoff, and a rejection with lastChance true ends the retries." caption="Background sync: queue, register, replay on the sync event, and the three ways the promise can settle." />

## What it does

When a fetch fails because the device is offline, a page can register a sync request
instead of giving up. The browser holds onto that registration and, once it decides the
device has connectivity, fires a `sync` event in the service worker so the app can retry
the deferred work — even if the page that originally registered it has since closed.

## The registration and event API

The API is reached through `ServiceWorkerRegistration.sync`, which returns a
`SyncManager`:

```js
async function queueMessage() {
  try {
    await sendMessage();
  } catch {
    const registration = await navigator.serviceWorker.ready;
    if ('sync' in registration) {
      await registration.sync.register('send-queued-message');
    }
  }
}
```

- **`SyncManager.register(tag)`** registers a one-off sync request under the given tag,
  returning a promise that resolves once registration completes.
- **`SyncManager.getTags()`** resolves with the list of tags currently registered.

In the service worker, the app listens for the `sync` event and passes the retry
work's promise to `waitUntil()`, which extends the event's lifetime. The user agent
can still impose its own execution and lifetime limits on that extended time, so
`waitUntil()` does not guarantee the work finishes — see "What goes wrong" below:

```js
self.addEventListener('sync', (event) => {
  if (event.tag === 'send-queued-message') {
    event.waitUntil(sendQueuedMessage());
  }
});
```

## Availability across browsers

Background sync is a Chromium (Blink) feature — Chrome, Edge, and Samsung Internet
implement it, while WebKit (Safari) and Gecko (Firefox) do not.

<CompatTable feature="background-sync" />

See the [compatibility page](/compatibility/background-sync/) for more detail.

## What goes wrong

- Per the spec, while the registering page or worker is still running, the event fires
  as soon as connectivity becomes available — not necessarily immediately. If that
  context is no longer running, the user agent should instead run the event at its
  "soonest convenience," not on any guaranteed schedule.
- The spec allows a user agent to impose its own execution and lifetime limits on a
  `SyncEvent` — calling `event.waitUntil()` does not guarantee the browser will let
  arbitrarily long work finish before terminating the worker.
- Background sync is unsupported in WebKit (Safari) and Gecko (Firefox) as of this
  writing — always design the retry as an enhancement, not the only path to success.
- Per the spec, a failed sync event may be retried according to user-agent-defined
  heuristics unless it was the final chance, at which point no further attempts are
  made — retries are not guaranteed to eventually succeed, and this is not a periodic
  schedule.

## How to detect it at runtime

Feature-detect `SyncManager` before registering a tag, and fall back to retrying
immediately (or on the next page load) when it's unavailable:

```js
function supportsBackgroundSync(registration) {
  return 'sync' in registration;
}

async function deferOrRetry(registration, tag, retryNow) {
  if (!supportsBackgroundSync(registration)) {
    return retryNow(); // no Background Sync: retry immediately instead
  }
  return registration.sync.register(tag);
}
```

## Where to go next

- [Background sync: browser support](/compatibility/background-sync/) — browser support
  matrix
- [Periodic background sync](/reference/service-worker/periodic-background-sync/) —
  a related, separate API