DEV Community

Yahya Muhammad
Yahya Muhammad

Posted on

What breaks when you move a Chrome extension to Manifest V3, and the order I fix it in

I have moved a fair number of extensions from Manifest V2 to V3 now. The code changes are not the hard part. The hard part is that four things break at once and people fix them in the wrong order, so they spend a week chasing a bug that comes from step one.

This is the order that works for me.

1. The background stops remembering things

In V2 the background page stayed alive. In V3 it is a service worker and Chrome shuts it down after about thirty seconds of nothing happening. Every global variable you had is gone when it wakes up.

So I do this first, before touching anything else:

  • every listener registered at the top level of the file, not inside a callback
  • every global turned into a chrome.storage read
  • every setInterval longer than a few seconds turned into chrome.alarms
// wrong: lost when the worker sleeps
let lastSync = 0;

// right: survives a restart
chrome.alarms.create("sync", { periodInMinutes: 5 });
chrome.alarms.onAlarm.addListener(async (a) => {
  if (a.name !== "sync") return;
  const { lastSync = 0 } = await chrome.storage.local.get("lastSync");
  await chrome.storage.local.set({ lastSync: Date.now() });
});
Enter fullscreen mode Exit fullscreen mode

If the extension works with the popup open and fails when it is closed, this is the reason nine times out of ten.

2. Blocking network requests in JavaScript is gone

webRequest blocking does not run any more. Rules move to declarativeNetRequest, a JSON file for the fixed ones and updateDynamicRules for the ones that change. It is less flexible. For most extensions it is enough.

3. A service worker has no DOM

DOMParser, clipboard and audio do not exist in the worker. In Chrome and Edge they go into an offscreen document. You give it one reason when you create it, and the store reviewer reads that line, so write a real one.

Firefox has no offscreen API. My code checks for it and skips that part instead of throwing.

4. Permissions

What the extension needs at install goes in host_permissions. Everything else goes in optional_host_permissions and gets requested at the moment the user does the thing. Reviews go faster this way. It is also the step people skip, and then the listing sits in review.

Two stores, one codebase

I do not keep a second manifest for Firefox. I keep a small overrides file and merge it at build time, so the two never drift apart. One command gives me a zip for Chrome and Edge and a zip for Firefox.

Before every submission I run web-ext lint. The Firefox reviewers run the same tool, so there is no point finding out from them.

The skeleton

I put the whole thing in a repo so I do not rebuild it each time: background, offscreen document, rules file, content script, the Firefox overrides and the build script.

mv3-extension-starter on GitHub

It is not perfect. The offscreen part does nothing useful on Firefox, and the rules file only covers the common cases. But it starts from a place where the worker restarting does not break anything, which is where most migrations go wrong.

If you would rather hand the migration over, my team does this work: Chrome extension developers.

What broke first in your migration? I am curious whether it was the worker for you too.

Top comments (0)