![Brendan Kenny](https://web.dev/images/authors/brendankenny.jpg) Brendan Kenny [GitHub](https://github.com/brendankenny) [Bluesky](https://bsky.app/profile/brendankenny.bsky.social)

<br />

It's common for web pages to need to send data (or "beacon") back to their server---think analytics data for a user's current session, as an example. For developers, this requires a balancing act: reducing constant, possibly redundant, requests without risking missed data if the tab was closed or the user navigated away before a beacon can be sent.

Traditionally, developers have used `pagehide` and `visibilitychange` events to catch the page as it unloads, and then use `navigator.sendBeacon()` or a `fetch()` with `keepalive` to beacon data. However, both of these events have difficult corner cases that differ based on the user's browser, and sometimes the events never arrive at all---especially on mobile.

`fetchLater()` is a proposal to replace this complexity with a single API call. It does exactly as its name suggests: it asks the browser to ensure a request is made at some point in the future, even if the page is closed or the user navigates away.

`fetchLater()` is available in Chrome for testing with real users behind an [origin trial](https://developer.chrome.com/origintrials#/view_trial/396879717162024961) starting in version 121 (released in January 2024), running until 3rd September 2024.

## The `fetchLater()` API

    const fetchLaterResult = fetchLater(request, options);

`fetchLater()` takes two arguments, generally identical to those of [`fetch()`](https://developer.mozilla.org/docs/Web/API/fetch#syntax):

- The `request`, either a string URL or a [`Request`](https://developer.mozilla.org/docs/Web/API/Request) instance.
- An optional `options` object, which extends the `options` from `fetch()` with a timeout called `activateAfter`.

`fetchLater()` returns a `FetchLaterResult`, currently containing only a single read-only property `activated`, which will be set to `true` when "later" has passed and the fetch has been made. Any response to the `fetchLater()` request is discarded.

### `request`

The simplest usage is a URL by itself:

    fetchLater('/endpoint/');

But, just like `fetch()`, a large number of options can be set on a `fetchLater()` request, including custom headers, credentials behavior, a `POST` body, and an [`AbortController` `signal` to potentially cancel it](https://developer.mozilla.org/docs/Web/API/AbortController/signal).

    fetchLater('/endpoint/', {
      method: 'GET',
      cache: 'no-store',
      mode: 'same-origin',
      headers: {Authorization: 'SUPER_SECRET'},
    });

### `options`

The options object extends `fetch()`'s options with a timeout, `activateAfter`, in case you want to fire the request after the timeout or when the page is unloaded, whichever comes first.

This lets you decide the tradeoff between getting data at the absolute last possible moment or when it's more timely.

For example, if you have an app your users usually keep open for the entire work day, you might want to have a timeout of an hour to ensure more granular analytics while still guaranteeing a beacon if the user exited at any time before that hour was up. A new `fetchLater()` can then be set up for the next hour of analytics.

    const hourInMilliseconds = 60 * 60 * 1000;
    fetchLater('/endpoint/', {activateAfter: hourInMilliseconds});

## Example use

One issue when measuring [Core Web Vitals](https://web.dev/articles/vitals) in the field is that any of the performance metrics could change until the user actually leaves a page. For example, larger layout shifts could happen at any time, or the page could take even longer to respond to an interaction.

However, you don't want to risk losing all performance data due to buggy or incomplete beaconing on page unload. It's a perfect candidate for `fetchLater()`.

In this example, the [web-vitals.js library](https://github.com/GoogleChrome/web-vitals) is used to monitor the metrics, and `fetchLater()` is used to report the results to an analytics endpoint:

    import {onCLS, onINP, onLCP} from 'web-vitals';

    const queue = new Set();
    let fetchLaterController;
    let fetchLaterResult;

    function updateQueue(metricUpdate) {
      // If there was an already complete request for whatever
      // reason, clear out the queue of already-sent updates.
      if (fetchLaterResult?.activated) {
        queue.clear();
      }

      queue.add(metricUpdate);

      // JSON.stringify used here for simplicity and will likely include
      // more data than you need. Replace with a preferred serialization.
      const body = JSON.stringify([...queue]);

      // Abort any existing `fetchLater()` and schedule a new one with
      // the update included.
      fetchLaterController?.abort();
      fetchLaterController = new AbortController();
      fetchLaterResult = fetchLater('/analytics', {
        method: 'POST',
        body,
        signal: fetchLaterController.signal,
        activateAfter: 60 * 60 * 1000, // Timeout to ensure timeliness.
      });
    }

    onCLS(updateQueue);
    onINP(updateQueue);
    onLCP(updateQueue);

Every time a metric update comes in, any existing scheduled `fetchLater()` is canceled with an [`AbortController`](https://developer.mozilla.org/docs/Web/API/AbortController) and a new `fetchLater()` is created with the update included.

## Try out `fetchLater()`

As stated, `fetchLater()` is available in an [origin trial](https://developer.chrome.com/origintrials#/view_trial/396879717162024961) until Chrome 126. See "[Get started with origin trials](https://developer.chrome.com/docs/web-platform/origin-trials)" for background information on origin trials

For local testing, `fetchLater` can be enabled with the Experimental Web Platform features flag at `chrome://flags/#enable-experimental-web-platform-features`. It can also be enabled by running Chrome from the command line with `--enable-experimental-web-platform-features`, or the more targeted `--enable-features=FetchLaterAPI` flag.

If you use it on a public page, make sure to feature detect by checking if the global `fetchLater` is defined before using it:

    if (globalThis.fetchLater) {
      // Set up beaconing using fetchLater().
      // ...
    }

> [!NOTE]
> **Note:** There are [some limitations with the supported API versus the expected final version](https://chromium.googlesource.com/chromium/src/+/main/docs/experiments/fetch-later.md#what_s-not-supported). For example, `fetchLater()` is not yet available in service workers, and `fetchLater()` will not beacon after a browser crash. See that documentation for more details and links to spec discussions.

## Feedback

Developer feedback is essential to getting new web APIs right, so [please file issues and feedback on GitHub](https://github.com/WICG/pending-beacon/issues).

## More information

- [`fetchLater()` API explainer](https://github.com/WICG/pending-beacon/blob/main/docs/fetch-later-api.md)
- [`fetchLater()` Chromium experiment docs](https://chromium.googlesource.com/chromium/src/+/main/docs/experiments/fetch-later.md)
- [Chrome Platform Status entry](https://chromestatus.com/feature/4654499737632768)
- [Chromium tracking issue](https://crbug.com/40276121)