You change "manifest_version": 2 to 3 and the extension breaks. Two errors show up a lot:

  • Service worker registration failed
  • chrome.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.