![Jake Archibald](https://web.dev/images/authors/jakearchibald.jpg) Jake Archibald [X](https://twitter.com/jaffathecake) [Homepage](https://jakearchibald.com/)

<br />

In 2015 we [introduced Background Sync](https://developer.chrome.com/blog/background-sync) which allows the
service worker to defer work until the user has connectivity. This means the user could type a
message, hit send, and leave the site knowing that the message will be sent either now, or when they
have connectivity.

It's a useful feature, but it requires the service worker to be alive for the duration of the
fetch. That isn't a problem for short bits of work like sending a message, but if the task takes
too long the browser will kill the service worker, otherwise it's a risk to the user's privacy and
battery.

So, what if you need to download something that might take a long time, like a movie, podcasts, or
levels of a game. That's what [Background Fetch](https://developer.mozilla.org/docs/Web/API/Background_Fetch_API) is for.

Background Fetch is available by default since Chrome 74.

Here's a quick two minute demo showing the traditional state of things, vs using Background Fetch:

[Video](https://www.youtube.com/watch?v=eLfgf2ZvFpo)

## How it works

A background fetch works like this:

1. You tell the browser to perform a group of fetches in the background.
2. The browser fetches those things, displaying progress to the user.
3. Once the fetch has completed or failed, the browser opens your service worker and fires an event to tell you what happened. This is where you decide what to do with the responses, if anything.

If the user closes pages to your site after step 1, that's ok, the download will continue. Because
the fetch is highly visible and easily abortable, there isn't the privacy concern of a way-too-long
background sync task. Because the service worker isn't constantly running, there isn't the concern
that it could abuse the system, such as mining bitcoin in the background.

On some platforms (such as Android) it's possible for the browser to close after step 1, as the
browser can hand off the fetching to the operating system.

If the user starts the download while offline, or goes offline during the download, the background
fetch will be paused and resumed later.

## The API

### Feature detect

As with any new feature, you want to detect if the browser supports it. For Background Fetch, it's
as simple as:

    if ('BackgroundFetchManager' in self) {
      // This browser supports Background Fetch!
    }

### Starting a background fetch

The main API hangs off a [service worker](https://developer.chrome.com/docs/workbox/service-worker-overview) registration,
so make sure you've registered a service worker first. Then:

    navigator.serviceWorker.ready.then(async (swReg) => {
      const bgFetch = await swReg.backgroundFetch.fetch('my-fetch', ['/ep-5.mp3', 'ep-5-artwork.jpg'], {
        title: 'Episode 5: Interesting things.',
        icons: [{
          sizes: '300x300',
          src: '/ep-5-icon.png',
          type: 'image/png',
        }],
        downloadTotal: 60 * 1024 * 1024,
      });
    });

> [!NOTE]
> **Note:** Many examples in this article use async functions. If you aren't familiar with them, [check
> out the guide](https://web.dev/articles/async-functions).

`backgroundFetch.fetch` takes three arguments:

| Parameters ||
|---|---|
| `id` | `string` uniquely identifies this background fetch. `backgroundFetch.fetch` will reject if the ID matches an existing background fetch. |
| `requests` | `Array<https://developer.mozilla.org/docs/Web/API/Request|string>` The things to fetch. Strings will be treated as URLs, and turned into `Request`s via `new Request(theString)`. You can fetch things from other origins as long as the resources allow it via [CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS). **Note:** Chrome doesn't currently support requests that would require a CORS preflight. |
| `options` | An object which may include the following: |
| `options.title` | `string` A title for the browser to display along with progress. |
| `options.icons` | `Array<https://developer.mozilla.org/docs/Web/Manifest>` An array of objects with a \`src\`, \`size\`, and \`type\`. |
| `options.downloadTotal` | `number` The total size of the response bodies (after being un-gzipped). Although this is optional, it's strongly recommended that you provide it. It's used to tell the user how big the download is, and to provide progress information. If you don't provide this, the browser will tell the user the size is unknown, and as a result the user may be more likely to abort the download. If the background fetch downloads exceeds the number given here, it will be aborted. It's totally fine if the download is smaller than the `downloadTotal`, so if you aren't sure what the download total will be, it's best to err on the side of caution. |

`backgroundFetch.fetch` returns a promise that resolves with a `BackgroundFetchRegistration`. I'll
cover the details of that later. The promise rejects if the user has opted out of downloads, or one
of the provided parameters is invalid.

Providing many requests for a single background fetch lets you combine things that are logically a
single thing to the user. For example, a movie may be split into 1000s of resources (typical with
[MPEG-DASH](https://developer.mozilla.org/docs/Web/Apps/Fundamentals/Audio_and_video_delivery/Live_streaming_web_audio_and_video#MPEG-DASH)),
and come with additional resources like images. A level of a game could be spread across many
JavaScript, image, and audio resources. But to the user, it's just "the movie", or "the level".

> [!CAUTION]
> **Caution:** Caution: Chrome's implementation currently only accepts requests without a body. In future, bodies will be allowed, meaning you can use background fetch for large uploads, such as photos and video.

### Getting an existing background fetch

You can get an existing background fetch like this:

    navigator.serviceWorker.ready.then(async (swReg) => {
      const bgFetch = await swReg.backgroundFetch.get('my-fetch');
    });

...by passing the **id** of the background fetch you want. `get` returns `undefined` if there's no
active background fetch with that ID.

A background fetch is considered "active" from the moment it's registered, until it either succeeds,
fails, or is aborted.

You can get a list of all the active background fetches using `getIds`:

    navigator.serviceWorker.ready.then(async (swReg) => {
      const ids = await swReg.backgroundFetch.getIds();
    });

### Background fetch registrations

A `BackgroundFetchRegistration` (`bgFetch` in the above examples) has the following:

| Properties ||
|---|---|
| `id` | `string` The background fetch's ID. |
| `uploadTotal` | `number` The number of bytes to be sent to the server. |
| `uploaded` | `number` The number of bytes successfully sent. |
| `downloadTotal` | `number` The value provided when the background fetch was registered, or zero. |
| `downloaded` | `number` The number of bytes successfully received. This value may decrease. For example, if the connection drops and the download cannot be resumed, in which case the browser restarts the fetch for that resource from scratch. |
| `result` | One of the following: - `""` - The background fetch is active, so there's no result yet. - `"success"` - The background fetch was successful. - `"failure"` - The background fetch failed. This value only appears when the background fetch totally fails, as in the browser cannot retry/resume. |
| `failureReason` | One of the following: - `""` - The background fetch hasn't failed. - `"aborted"` -- The background fetch was aborted by the user, or `abort()` was called. - `"bad-status"` - One of the responses had a not-ok status, e.g. 404. - `"fetch-error"` - One of the fetches failed for some other reason, e.g. CORS, MIX, an invalid partial response, or a general network failure for a fetch that cannot be retried. - `"quota-exceeded"` - Storage quota was reached during the background fetch. - `"download-total-exceeded"` - The provided \`downloadTotal\` was exceeded. |
| `recordsAvailable` | `boolean` Can the underlying requests/responses can be accessed? Once this is false `match` and `matchAll` cannot be used. |
| `abort()` | Returns `Promise<boolean>` Abort the background fetch. The returned promise resolves with true if the fetch was successfully aborted. |
| `matchAll(request, opts)` | Returns `Promise<Array<BackgroundFetchRecord>>` Get the requests and responses. The arguments here are the same as [the cache API](https://developer.mozilla.org/docs/Web/API/Cache/match#Parameters). Calling without arguments returns a promise for all records. See below for more details. |
| `match(request, opts)` | Returns `Promise<BackgroundFetchRecord>` As above, but resolves with the first match. |
| `progress` | Fired when any of `uploaded`, `downloaded`, `result`, or `failureReason` change. |

### Tracking progress

This can be done via the `progress` event. Remember that `downloadTotal` is whatever value you
provided, or `0` if you didn't provide a value.

    bgFetch.addEventListener('progress', () => {
      // If we didn't provide a total, we can't provide a %.
      if (!bgFetch.downloadTotal) return;

      const percent = Math.round(bgFetch.downloaded / bgFetch.downloadTotal * 100);
      console.log(`Download progress: ${percent}%`);
    });

### Getting the requests and responses

> [!CAUTION]
> **Caution:** In Chrome's current implementation you can only get the requests and responses during `backgroundfetchsuccess`, `backgroundfetchfailure`, and `backgroundfetchabort` service worker events (see below). In future you'll be able to get in-progress fetches.

    bgFetch.match('/ep-5.mp3').then(async (record) => {
      if (!record) {
        console.log('No record found');
        return;
      }

      console.log(`Here's the request`, record.request);
      const response = await record.responseReady;
      console.log(`And here's the response`, response);
    });

`record` is a `BackgroundFetchRecord`, and it looks like this:

| Properties ||
|---|---|
| `request` | `Request` The request that was provided. |
| `responseReady` | `Promise<Response>` The fetched response. The response is behind a promise because it may not have been received yet. The promise will reject if the fetch fails. |

### Service worker events

| Events ||
|---|---|
| `backgroundfetchsuccess` | Everything was fetched successfully. |
| `backgroundfetchfailure` | One or more of the fetches failed. |
| `backgroundfetchabort` | One or more fetches failed. This is only really useful if you want to perform clean-up of related data. |
| `backgroundfetchclick` | The user clicked on the download progress UI. |

The event objects have the following:

| Properties ||
|---|---|
| `registration` | `BackgroundFetchRegistration` |
| `updateUI({ title, icons })` | Lets you change the title/icons you initially set. This is optional, but it lets you provide more context if necessary. You can only do this \*once\* during `backgroundfetchsuccess` and `backgroundfetchfailure` events. |

### Reacting to success/failure

We've already seen the `progress` event, but that's only useful while the user has a page open to
your site. The main benefit of background fetch is things continue to work after the user leaves the
page, or even closes the browser.

If the background fetch successfully completes, your service worker will receive the
`backgroundfetchsuccess` event, and `event.registration` will be the background fetch registration.

After this event, the fetched requests and responses are no longer accessible, so if you want to
keep them, move them somewhere like the [cache API](https://developer.mozilla.org/docs/Web/API/CacheStorage).

As with most service worker events, use `event.waitUntil` so the service worker knows when the event
is complete.

> [!NOTE]
> **Note:** Note: You can't hold the service worker open indefinitely here, so avoid doing things that would keep the service worker open a long time here, such as additional fetching.

For example, in your service worker:

    addEventListener('backgroundfetchsuccess', (event) => {
      const bgFetch = event.registration;

      event.waitUntil(async function() {
        // Create/open a cache.
        const cache = await caches.open('downloads');
        // Get all the records.
        const records = await bgFetch.matchAll();
        // Copy each request/response across.
        const promises = records.map(async (record) => {
          const response = await record.responseReady;
          await cache.put(record.request, response);
        });

        // Wait for the copying to complete.
        await Promise.all(promises);

        // Update the progress notification.
        event.updateUI({ title: 'Episode 5 ready to listen!' });
      }());
    });

Failure may have come down to a single 404, which may not have been important to you, so it might
still be worth copying some responses into a cache as above.

### Reacting to click

The UI displaying the download progress and result is clickable. The `backgroundfetchclick` event in
the service worker lets you react to this. As above `event.registration` will be the background
fetch registration.

The common thing to do with this event is open a window:

    addEventListener('backgroundfetchclick', (event) => {
      const bgFetch = event.registration;

      if (bgFetch.result === 'success') {
        clients.openWindow('/latest-podcasts');
      } else {
        clients.openWindow('/download-progress');
      }
    });

## Additional resources

- [Explainer](https://github.com/WICG/background-fetch/blob/main/README.md)
- [Specification](https://wicg.github.io/background-fetch/)
- [chromestatus.com entry](https://www.chromestatus.com/features/5712608971718656)

*Correction: A previous version of this article incorrectly referred to Background Fetch as being a "web standard". The API is not currently on the standards track, the [specification](https://wicg.github.io/background-fetch/) can be found in WICG as a Draft Community Group Report.*