· deepdives  · 7 min read

Unlocking the Power of the Background Fetch API: A Step-by-Step Guide

Learn what the Background Fetch API is, how it works, and how to implement it in a Progressive Web App to handle long-running downloads/uploads in the background. Includes step-by-step code, real-world use cases, fallbacks, and testing tips.

Learn what the Background Fetch API is, how it works, and how to implement it in a Progressive Web App to handle long-running downloads/uploads in the background. Includes step-by-step code, real-world use cases, fallbacks, and testing tips.

Why the Background Fetch API?

Long-running network tasks - large file downloads, multi-image galleries, or big uploads - are painful to manage in single-page web apps: users close the page, navigate away, or lose connectivity and the task aborts. The Background Fetch API lets you delegate those tasks to the browser via a Service Worker so they can continue (and report progress) even after the user leaves your site.

Key benefits:

  • Runs network tasks independently of the page lifecycle.
  • Lets browser present a persistent UI for progress (platform-dependent).
  • Handles retries and continuation across connectivity changes.

Note: Browser support is limited and evolving. See the compatibility section below.

How it works (high level)

  1. The page asks the Service Worker (via ServiceWorkerRegistration.backgroundFetch.fetch) to fetch one or more resources.
  2. The fetch is managed by the browser outside the page; progress and lifecycle live with the Service Worker.
  3. The Service Worker receives lifecycle events (success, failure, clicks) and can access the downloaded responses or store them (e.g., to IndexedDB or Cache API).

Browser support and limitations

  • Background Fetch is a relatively new and experimental API with limited support in browsers. Check current compatibility before using in production.
  • It requires a secure context (HTTPS) and a Service Worker.
  • Not all platforms surface the same UI. Some browsers may not implement a persistent system-level UI; they still run the fetch logic but you’ll need to provide your own app UI for progress.

References:

Before you begin: prerequisites

  • Serve your site over HTTPS.
  • Register a Service Worker with a scope that covers the pages that will request background fetches.
  • Test on a browser that supports the API (check the links above).

Step-by-step implementation

Below is a practical example that shows how to:

  • Register a Service Worker
  • Trigger a background fetch from the page
  • Handle background fetch events inside the Service Worker and save responses to the Cache

1) Register the Service Worker (in your main page)

if ('serviceWorker' in navigator) {
  navigator.serviceWorker
    .register('/sw.js')
    .then(reg => console.log('SW registered', reg))
    .catch(err => console.error('SW registration failed', err));
}

Make sure /sw.js is under the scope you want for background fetches.

2) Trigger a Background Fetch from the page

From a user gesture (best practice), request the background fetch via the registration’s backgroundFetch manager:

async function startBackgroundDownload(urls) {
  if (!('serviceWorker' in navigator))
    throw new Error('No service worker support');

  const reg = await navigator.serviceWorker.ready;

  if (!reg.backgroundFetch) {
    throw new Error('Background Fetch not supported in this browser');
  }

  try {
    // id can be any string that identifies this fetch
    const bgFetch = await reg.backgroundFetch.fetch('my-download-id', urls, {
      title: 'Downloading assets',
      icons: [
        {
          sizes: '192x192',
          src: '/icons/download-icon.png',
          type: 'image/png',
        },
      ],
      downloadTotal: 0, // optional: unknown total size
    });

    console.log('Background fetch started', bgFetch);
  } catch (err) {
    console.error('Background fetch failed to start', err);
  }
}

// Example usage (must be called after a user gesture):
const assetUrls = [
  '/large-video.mp4',
  '/high-res-image-1.jpg',
  '/high-res-image-2.jpg',
];
startBackgroundDownload(assetUrls);

Notes:

  • urls can be an array of strings or Request objects.
  • The options object can include title, icons, and downloadTotal.

3) Handle events in the Service Worker

In /sw.js, listen for Background Fetch events. The main lifecycle events are backgroundfetchsuccess, backgroundfetchfail, and backgroundfetchabort. On success, you typically want to obtain the responses and persist them (Cache API, IndexedDB, or file system API if available).

self.addEventListener('backgroundfetchsuccess', event => {
  // Keep service worker alive until work completes
  event.waitUntil(
    (async () => {
      const bgFetch = event.registration;

      // Get the records for individual requests
      const records = await bgFetch.matchAll();

      // Open a cache to store downloaded responses
      const cache = await caches.open('background-fetch-cache-v1');

      for (const record of records) {
        // `record` has a `responseReady` Promise that resolves with a Response
        const response = await record.responseReady;

        // Choose how you want to store the response. Example: Cache API
        const request = record.request;
        await cache.put(request, response);
      }

      // Optionally update the UI shown by the platform
      try {
        await bgFetch.updateUI({
          title: 'Download complete',
        });
      } catch (err) {
        // updateUI may not be supported in all browsers
        console.warn('updateUI not supported', err);
      }

      // You can also show a notification
      self.registration.showNotification('Downloads ready', {
        body: 'Your background downloads are finished.',
        icon: '/icons/notify-icon.png',
      });
    })()
  );
});

self.addEventListener('backgroundfetchfail', event => {
  event.waitUntil(
    (async () => {
      // Handle failure: inform the user, clean up partial data, retry logic, etc.
      console.warn('Background fetch failed', event.registration.id);
      self.registration.showNotification('Download failed', {
        body: 'Some files failed to download in the background.',
      });
    })()
  );
});

self.addEventListener('backgroundfetchabort', event => {
  // User cancelled or the fetch was aborted
  console.log('Background fetch was aborted', event.registration.id);
});

// Optional: handle a user click on the background fetch UI
self.addEventListener('backgroundfetchclick', event => {
  event.waitUntil(
    (async () => {
      // Focus or open the relevant client page
      const clientsList = await clients.matchAll({
        type: 'window',
        includeUncontrolled: true,
      });
      if (clientsList.length > 0) {
        clientsList[0].focus();
      } else {
        clients.openWindow('/downloads');
      }
    })()
  );
});

Important implementation notes:

  • Each record returned by matchAll() represents one request/response pair.
  • Access the response using record.responseReady - a Promise that resolves to a Response.
  • Use event.waitUntil() to keep the Service Worker alive while you process the data.

4) Reading cached items in your page

Later, in a page, you can read the cached responses from the Cache API or from whatever storage you used:

async function showDownloadedAssets() {
  const cache = await caches.open('background-fetch-cache-v1');
  const keys = await cache.keys();
  for (const request of keys) {
    const response = await cache.match(request);
    // Do whatever you need with the response (create blob URLs, display images, etc.)
  }
}

Real-world scenarios and patterns

  • Offline-first media downloads: let users queue podcasts, videos, or image galleries to download while they continue using other sites or lock their phone.
  • Large file uploads: when uploading many/large files, use Background Fetch where supported so the upload continues if the user navigates away (server-side must support idempotent/resumable uploads).
  • App update assets: download optional feature bundles, maps, or game assets in the background.
  • E-commerce receipts: upload large receipts or images in the background after the purchase completes.

Handling unsupported browsers (fallbacks)

Because support is limited, implement graceful fallbacks:

  • Detect support (check serviceWorkerRegistration.backgroundFetch) and fall back to an in-page fetch with progress and a clear UX warning that the task will stop if the page is closed.
  • Use Background Sync (SyncManager) for smaller or resumable uploads when Background Fetch isn’t available.
  • Implement chunked uploads with IndexedDB + periodic background sync or server-side resumable upload endpoints.

Example feature detect:

const swReg = await navigator.serviceWorker.ready;
if (swReg.backgroundFetch) {
  // use Background Fetch
} else {
  // fallback: start fetch and show progress in-page, or queue via IndexedDB
}

Testing, debugging, and best practices

  • Must be served over HTTPS.
  • Trigger background fetches via a user gesture where possible to avoid blockers.
  • Use realistic large files in testing so the background workflow is exercised.
  • Use DevTools (Application tab) and the Service Worker pane to monitor registrations. Some browsers include a Background Fetch section in DevTools; support varies.
  • Remember to clean up caches and data after use to avoid filling device storage.
  • Be mindful of battery and data usage. Respect user preferences and offer clear opt-in UX for large background transfers.

Security and privacy considerations

  • Only fetch from origins and endpoints you control or trust. Background fetch continues outside the page, so be careful with credentials and tokens.
  • If using credentials, ensure proper CORS and credentials handling on the server side.
  • Provide transparent UI so users know large background transfers may consume data.

When not to use Background Fetch

  • Small, ephemeral requests - Background Fetch adds complexity and isn’t needed for short operations.
  • When you need precise control of partial progress at the page level and can keep the page open for the transfer.

Summary / Checklist

  • Ensure HTTPS and a Service Worker are in place.
  • Feature-detect serviceWorkerRegistration.backgroundFetch.
  • Start background fetches from a user gesture.
  • Handle lifecycle events in the Service Worker and persist downloaded responses as needed.
  • Provide fallbacks for unsupported browsers and be mindful of UX for battery/data.

Background Fetch is a powerful tool for enabling long-running network tasks in PWAs and other web apps. When supported, it lets you deliver a better user experience by taking heavy work out of the foreground page and letting the browser manage the lifecycle.

References

Back to Blog

Related Posts

View All Posts »
Mastering Periodic Background Sync: A Step-by-Step Guide

Mastering Periodic Background Sync: A Step-by-Step Guide

A comprehensive tutorial that walks through implementing the Periodic Background Sync API in service workers, covers browser compatibility, testing strategies, real-world use cases, and best practices for battery and privacy-aware background updates.