You change "manifest_version": 2 to 3 and the extension breaks. Two errors show up a lot:
Service worker registration failedchrome.browserAction is undefined
Below is a small made-up teaching example (a tab counter), not a real project or client case. It is a partial migration, checked by reading the files only; it was not run in a browser.
1. The background page has to become a service worker
MV2 (before):
"background": { "scripts": ["background.js"], "persistent": true },
"browser_action": { "default_title": "Tab count" }
MV3 (after):
"background": { "service_worker": "background.js" },
"action": { "default_title": "Tab count" }
MV3 has no persistent background pages. browser_action was renamed action, and chrome.browserAction became chrome.action in code.
2. Don't keep state in a global variable
Before:
let count = 0; // in-memory state: lost when an MV3 worker stops
function refresh() {
chrome.tabs.query({}, (tabs) => {
count = tabs.length;
chrome.browserAction.setBadgeText({ text: String(count) });
});
}
chrome.tabs.onCreated.addListener(refresh);
chrome.tabs.onRemoved.addListener(refresh);
refresh();
The service worker is stopped when idle, so count is lost. After:
async function refresh() {
const tabs = await chrome.tabs.query({});
await chrome.action.setBadgeText({ text: String(tabs.length) });
}
chrome.tabs.onCreated.addListener(refresh);
chrome.tabs.onRemoved.addListener(refresh);
chrome.runtime.onStartup.addListener(refresh);
chrome.runtime.onInstalled.addListener(refresh);
What changed:
| Change | Before (MV2) | After (MV3) |
|---|---|---|
| Background |
scripts + persistent: true
|
service_worker |
| Toolbar API |
browser_action / chrome.browserAction
|
action / chrome.action
|
| State | global count
|
recomputed on each event |
| Callbacks | tabs.query(cb) |
await tabs.query() |
| Startup | top-level refresh()
|
onStartup / onInstalled
|
Listeners stay registered at the top level so they are there whenever the worker restarts.
What this does not cover
Content scripts, webRequest → declarativeNetRequest, removing remote code, splitting host_permissions, options pages, and Chrome Web Store review. Each one is a separate job. Check the official Chrome MV3 migration guide before relying on this.
Disclosure: this article was generated by AI from a synthetic teaching example. It was first published on Hashnode.
Top comments (0)