Migrate from Workbox v4 to v5

This guide is focused on breaking changes introduced in Workbox v5, with examples of what changes you'd need to make when upgrading from Workbox v4.

Breaking Changes

Plugin Classes Renamed

A number of Workbox v4 packages included classes named Plugin. In v5, those classes have been renamed to follow the pattern package identifier + Plugin:

  • BackgroundSyncPlugin
  • BroadcastUpdatePlugin
  • CacheableResponsePlugin
  • ExpirationPlugin
  • RangeRequestsPlugin

This renaming applies whether you're using the classes via module imports or via the workbox.* namespaces.

Default Precache Manifest Replacement Point

Previously, when using one of the build tools in "inject manifest" mode, your source service worker file was checked for the presence of precacheAndRoute([]), with that empty array [] used as a placeholder for the point at which the precache manifest was injected.

In Workbox v5, the replacement logic has changed, and now self.__WB_MANIFEST is used by default as the injection point.

// v4:

// v5:

As outlined in this discussion, we believe this change provides a simpler experience, while simultaneously giving developers more control over how the injected manifest is used within custom service worker code. If needed, you can change this replacement string via the injectionPoint configuration option.

Two options that were previously supported for navigation routes, blacklist and whitelist have been renamed denylist and allowlist.

workbox-routing previously supported a method, registerNavigationRoute(), that, under the hood, did two things:

  1. Detected whether or not a given fetch event had a mode of 'navigate'.
  2. If so, responded to that request using the contents of a previously cached, hardcoded URL, regardless of the URL being navigated to.

This is a common pattern to use when implementing the App Shell architecture.

The second step, generating a response by reading from the cache, falls outside of what we see as the responsibilities of workbox-routing. Instead, we see it as functionality that should be part of workbox-precaching, via a new method, createHandlerBoundToURL(). This new method can work hand-in-hand with the existing NavigationRoute class in workbox-routing to accomplish the same logic.

If you're using the navigateFallback option in one of the build tool's "generate SW" mode, then the switchover will happen automatically. If you previously configured either the navigateFallbackBlacklist or navigateFallbackWhitelist options, change those to navigateFallbackDenylist or navigateFallbackAllowlist, respectively.

If you're using "inject manifest" mode or just writing the service worker yourself, and your Workbox v4 service worker calls registerNavigationRoute() directly, then you'll have to make a change to your code to get the equivalent behavior.

// v4:
import {getCacheKeyForURL} from 'workbox-precaching';
import {registerNavigationRoute} from 'workbox-routing';

const appShellCacheKey = getCacheKeyForURL('/app-shell.html');
registerNavigationRoute(appShellCacheKey, {
  whitelist: [...],
  blacklist: [...],

// v5:
import {createHandlerBoundToURL} from 'workbox-precaching';
import {NavigationRoute, registerRoute} from 'workbox-routing';

const handler = createHandlerBoundToURL('/app-shell.html');
const navigationRoute = new NavigationRoute(handler, {
  allowlist: [...],
  denylist: [...],

You no longer need to call getCacheKeyForURL(), as createHandlerBoundToURL() will take care of that for you.

Removal of makeRequest() from workbox-strategies

Calling makeRequest() is mostly equivalent to calling handle() on one of the workbox-strategy classes. The differences between the two methods were so slight that keeping both around did not make sense. Developers who called makeRequest() should be able to switch to using handle() without any further change:

// v4:
const strategy = new StaleWhileRevalidate({...});
const response = await strategy.makeRequest({event, request});

// v5:
const strategy = new StaleWhileRevalidate({...});
const response = await strategy.handle({event, request});

In v5, handle() treats request as a required parameter, and will not fall back to using event.request. Make sure that you pass in a valid request when calling handle().

workbox-broadcast-update Always Uses postMessage()

In v4, the workbox-broadcast-update library would default to using the Broadcast Channel API for sending messages when it was supported, and fall back to using postMessage() only when Broadcast Channel wasn't supported.

We realized that having to listen for two potential sources of incoming messages made writing client-side code overly complicated. Additionally, on some browsers, postMessage() calls from the service worker sent to client pages are automatically buffered until a message event listener is set up. There is no buffering with the Broadcast Channel API, and broadcasted messages are just dropped if sent before a client page is ready to receive them.

Because of those reasons, we've changed workbox-broadcast-update to always use postMessage() in v5. Messages are sent one-by-one to all client pages within scope of the current service worker.

To accommodate this new behavior, you can remove any code you had in client pages that created BroadcastChannel instances, and instead, set up a message event listener on navigator.serviceWorker:

// v4:
const updatesChannel = new BroadcastChannel('api-updates');
updatesChannel.addEventListener('message', event => {
  const {cacheName, updatedUrl} = event.data.payload;
  // ... your code here ...

// v5:
// This listener should be added as early as possible in your page's lifespan
// to ensure that messages are properly buffered.
navigator.serviceWorker.addEventListener('message', event => {
  // Optional: ensure the message came from workbox-broadcast-update
  if (event.meta === 'workbox-broadcast-update') {
    const {cacheName, updatedUrl} = event.data.payload;
    // ... your code here ...

workbox-window users should not need to make any changes, as its internal logic has been updated to listen for postMessage() calls.

Build Tools Require Node.js v8 or Higher

Node.js versions prior to v8 are no longer supported for workbox-webpack-plugin, workbox-build, or workbox-cli. If you're running a Node.js version prior to 8, update your runtime to a supported version.

workbox-webpack-plugin Requires webpack v4 or Higher

If you're using workbox-webpack-plugin, update your webpack setup to use at least webpack v4.

Build Tool Option Overhaul

A number of workbox-build, workbox-cli, and workbox-webpack-plugin configuration parameters are no longer supported. For instance, generateSW will always create a local Workbox runtime bundle for you, so the importWorkboxFrom option no longer makes sense.

Consult the relevant tool's documentation for the lists of supported options.

Removal of generateSWString from workbox-build

The generateSWString mode has been removed from workbox-build. We expect the impact of this to be minimal, as it was primarily used internally by workbox-webpack-plugin.

Optional Changes

Using Module Imports

While this change is a) optional and b) technically was possible when using Workbox v4, the biggest change that we anticipate while moving to v5 is a model where you create your own bundled service worker by importing Workbox's modules. This approach is an alternative to calling importScripts('/path/to/workbox-sw.js') at the top of your service worker, and using Workbox via the workbox.* namespace.

If you're using one of the build tools (workbox-webpack-plugin, workbox-build, workbox-cli) in "generate SW" mode, then this change will happen for you automatically. All of those tools will output a local, custom bundle of the Workbox runtime alongside the actual code necessary to implement your service worker logic. In this scenario, there is no longer any dependency on workbox-sw or the CDN copy of Workbox. Depending on the value of your inlineWorkboxRuntime configuration, the Workbox runtime will either be split into a separate file that should be deployed alongside your service worker (when set to false, which is the default), or included inline along with the service worker logic (when set to true).

If you're using the build tools in "inject manifest" mode, or if you're not using Workbox's build tools at all, you can learn more about creating your own Workbox runtime bundle in the existing Using Bundlers (webpack/Rollup) with Workbox guide.

The documentation and examples for v5 are written assuming the module imports syntax, though the workbox.* namespace will continue to be supported in Workbox v5.

Reading Precached Responses

Some developers need to read precached responses directly from the cache, instead of implicitly using them via the precacheAndRoute() method. A common pattern in v4 would be to first get the cache key specific to the current version of a precached resource, and then pass in that key along with the precache's cache name to caches.match() to get the Response.

To simplify this process, workbox-precaching in v5 supports a new, equivalent method, matchPrecache():

// v4:
import {cacheNames} from 'workbox-core';
import {getCacheKeyForURL} from 'workbox-precaching';

const cachedResponse = await caches.match(
    cacheName: cacheNames.precache,

// v5:
import {matchPrecache} from 'workbox-precaching';

const cachedResponse = await matchPrecache(`/somethingPrecached`);

TypeScript Adoption

In v5, the Workbox runtime libraries are written in TypeScript. While we will continue to publish transpiled JavaScript modules and bundles to accommodate developers who have not adopted TypeScript, if you are using TypeScript, you should benefit from accurate, always up-to-date type information directly from the Workbox project.

Example Migration

This commit illustrates is a fairly involved migration, with inline commentary. It uses Rollup to include a custom Workbox runtime in the final service worker instead of loading the runtime from the CDN.

While it doesn't cover every breaking change, here's the before and after of upgrading one service worker file from v4 to v5, including a switch to TypeScript.

Getting Help

We anticipate most migrations to be straightforward. If you run into issues not covered in this guide, let us know by opening an issue on GitHub.