Skip to content

How to Migrate a Chrome Extension from Manifest V2 to V3

Chrome has removed the remaining Manifest V2 extensions from the Web Store. Learn how to migrate a Chrome extension to Manifest V3, including service workers, permissions, scripting, storage, and network rules.

How to Migrate a Chrome Extension from Manifest V2 to V3

On this page

August 31, 2026 marked the final Chrome Web Store removal of the remaining Manifest V2 extensions. If you maintain an older Chrome extension, the practical task now is not waiting for another compatibility switch; it is converting the extension to Manifest V3 and replacing the APIs and background architecture that V2 relied on.

Start by checking what your extension actually uses

Before changing code, open the extension's manifest.json and identify its background scripts, permissions, content scripts, injected code, and network-request handling. Manifest V3 is not just a version number change. Chrome replaced persistent background pages with service workers, moved script injection to the chrome.scripting API, changed how network requests can be modified, and removed support for remotely hosted extension code.

Make a small migration checklist before editing anything. Record every API your extension calls, every permission it requests, and every file loaded by the background page. This prevents a common migration mistake: changing the manifest successfully while leaving V2-only behavior hidden somewhere in the JavaScript.

Change the manifest from V2 to V3 first

Open manifest.json and change manifest_version from 2 to 3. A minimal V3 manifest looks like this:

{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "A simple Chrome extension"
}

Chrome's current extension platform accepts Manifest V3 as the supported manifest format. However, changing this number alone does not migrate an extension. The next changes depend on what your existing V2 extension does, so test the extension after each major migration step rather than changing everything at once.

Replace the V2 background page with a service worker

The biggest architectural change is the background environment. Manifest V2 could keep a background page running continuously, while Manifest V3 replaces it with an extension service worker that starts when an event needs to be handled and can stop when the work is finished.

Suppose the old extension contains this V2 configuration:

{
  "background": {
    "scripts": [
      "background.js"
    ],
    "persistent": false
  }
}

Change it to a V3 service worker:

{
  "background": {
    "service_worker": "service-worker.js"
  }
}

Then move the background event listeners into service-worker.js. The important difference is that you should not assume variables stored in memory will survive indefinitely. A service worker can stop when it is no longer needed, so persistent state belongs in an appropriate storage mechanism rather than in ordinary global variables.

Move persistent data out of localStorage

One V2 pattern that can quietly break during migration is using window.localStorage inside background code. Extension service workers do not provide the normal window-based storage interface. Chrome recommends using extension storage such as chrome.storage.local, or moving work that genuinely needs a document environment into an offscreen document.

For example, instead of relying on a background page to hold a setting in localStorage, declare the storage permission and save the value through the Chrome storage API:

{
  "manifest_version": 3,
  "permissions": [
    "storage"
  ]
}
chrome.storage.local.set({
  theme: "dark"
});

Later, read it asynchronously when the service worker needs it:

const result = await chrome.storage.local.get("theme");
console.log(result.theme);

This approach also fits the service-worker model better because the stored value remains available after the worker has stopped and restarted.

Replace tabs.executeScript with chrome.scripting

If the old extension injects JavaScript into the current page, look for V2 calls such as chrome.tabs.executeScript(). Manifest V3 moves this capability to chrome.scripting.executeScript(). You must also declare the scripting permission and provide either appropriate host permissions or the temporary activeTab permission.

For an extension that only needs to operate on the page after the user clicks its toolbar button, activeTab can be a useful narrower permission. It gives the extension temporary access to the active tab after a user action instead of requesting broad access to every website.

{
  "manifest_version": 3,
  "permissions": [
    "scripting",
    "activeTab"
  ],
  "action": {
    "default_title": "Run script"
  },
  "background": {
    "service_worker": "service-worker.js"
  }
}

The service worker can then inject a packaged script when the extension action is clicked:

chrome.action.onClicked.addListener(async (tab) => {
  if (!tab.id) return;

await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["content.js"]
});
});

This is more than a renamed API. The permission model is part of the migration, so an extension that changes the JavaScript call but forgets the required manifest permissions will fail when it tries to inject the script.

Review permissions instead of copying them blindly

Use the migration as an opportunity to remove permissions the extension no longer needs. Chrome separates API permissions from host permissions, and some access can be requested only when a feature actually needs it. For example, an extension that works on the page the user has deliberately selected may be able to use activeTab rather than requesting access to every site.

This matters for both security and user trust. A permission tells Chrome what an extension is capable of accessing, so copying a large V2 permission list into V3 can leave the migrated extension with more authority than its current functionality requires. Keep only the permissions needed by the code you actually ship.

Replace blocking webRequest logic where necessary

Extensions that modify network requests require special attention. Manifest V3 changes the old blocking webRequest model and provides declarativeNetRequest for many request-filtering and modification tasks. Instead of running arbitrary extension code for every matching request, declarative rules describe what Chrome should do.

This is one area where a mechanical search-and-replace migration is not enough. If your extension is an ad blocker, privacy tool, redirector, or request modifier, first map each V2 rule to the capabilities available in declarativeNetRequest. Some advanced V2 behaviors do not have a direct one-for-one replacement, so test the actual feature rather than assuming that changing the API name preserves its behavior.

Remove remotely hosted extension code

Manifest V3 does not allow an extension to depend on JavaScript hosted remotely and execute it as part of the extension. Code that belongs to the extension needs to be included in its package and go through the Chrome Web Store review process with the rest of the extension.

Search the project for external script loading and dynamic code patterns before publishing. A page that fetches ordinary data from an API is a different situation from downloading executable JavaScript and treating it as extension code. Keep application logic inside the extension package and use network requests for data rather than for code delivery.

Load the migrated extension locally before publishing

After the migration, test the unpacked extension in Chrome rather than immediately uploading it to the Chrome Web Store. Open the Extensions page, enable Developer mode, choose the option to load an unpacked extension, and select the directory containing the new manifest.json.

Then test the extension as a user would: click its toolbar action, open every important page, trigger its content scripts, change settings, restart Chrome, and exercise any network-related features. Open the extension's service-worker inspection tools when something fails. Service-worker errors can reveal assumptions from the old persistent background page that are no longer valid.

Test the cases that V2 developers often miss

Do not stop after confirming that the extension icon appears. Test what happens after the service worker has gone idle and starts again, because state that existed only in memory can disappear between events. Test permissions on pages where the extension should work and pages where it should not. If the extension injects scripts, verify that injection happens only after the intended user action or on the hosts you explicitly allow.

Also test upgrades from the currently installed version. A real user may already have stored settings or extension data created by the V2 version, so a clean installation is not enough. If your migration changes storage formats, include a migration path that detects old data and converts it before the new code depends on it.

Know what August 31 changed for old extensions

Chrome's published timeline says that all remaining Manifest V2 extensions were removed from the Chrome Web Store on August 31, 2026. Chrome 138 or earlier could keep an already-installed V2 extension, but those installations could not receive updates and could not be reinstalled from the Web Store after removal.

There is an important distinction here: the Web Store removal is not the same as the earlier runtime shutdown. Chrome's timeline says Manifest V2 extensions were disabled for users on Chrome 138 and later, with the enterprise policy exception ending with Chrome 139. That means developers should not treat the August 31 Web Store event as the first point at which V2 became unusable. The final removal mainly closes the remaining distribution path for extensions that had not already migrated.

Use the migration as a code cleanup, not just a manifest upgrade

A successful V2-to-V3 migration ends with more than a "manifest_version": 3 entry. The background architecture should work with service-worker lifecycles, persistent data should use suitable storage, script injection should use the V3 scripting model, permissions should match the extension's real needs, and network handling should use the appropriate V3 APIs.

Start with a small branch of the existing extension, migrate one subsystem at a time, and test after each change. If the extension has complicated request filtering or a large background system, treat those parts as architectural changes rather than simple API replacements. That approach takes longer than editing the manifest, but it leaves you with an extension that matches the platform Chrome is actually maintaining.

M

Written by

M. Rizwan Mirza

I’m M. Rizwan Mirza, a Full Stack Developer with over 12 years of experience in web development and software solutions. I work with modern web technologies and enjoy building practical, reliable, and user-friendly digital solutions. I’m also part of TechWare House, where I work on web development projects and technology solutions. One of my favorite websites is TheQuranic.com. Through WizTechnoz, I share my knowledge, experience, tutorials, and useful insights about technology.

44 posts published

All posts by this author

0 Comments

No comments yet. Be the first to share your thoughts.

Join the conversation

Log in or create a free account to leave a comment. You can edit or delete your own comments any time.