Update your code: Chrome Extension Migrate Manifest v3

Updating your code from Manifest V2 to V3 is a crucial step in ensuring your Chrome extension remains functional and secure. Manifest V3 introduces significant changes that impact how extensions are built and operated, including modifications to background scripts, permissions, and web request handling. This guide will walk you through the essential updates needed for your code to comply with the new standards set by Manifest V3, ensuring a smooth transition and optimal performance for your extension. Created by Exmo – a monetization platform for browser extensions.
Previos posts:
Note: Manifest V3 is generally supported in Chrome 88 or later. For information on support for extension features added in later Chrome versions, refer to the API reference documentation. If your extension requires a specific API, you can specify a minimum Chrome version in the manifest file.
This section outlines the necessary changes for code that is not part of the extension service worker. These changes are unrelated to other issues. The following two sections will cover replacing blocking web requests and enhancing security.
Replace tabs.executeScript() with scripting.executeScript()
In Manifest V3, the executeScript()
method transitions from the tabs API to the scripting API. This transition entails adjustments to permissions in the manifest file as well as actual code modifications.
For the executeScript()
method, you’ll need:
- The “
scripting
” permission. - Either host permissions or the “
activeTab
” permission.
The scripting.executeScript()
method operates similarly to tabs.executeScript()
, with a few differences:
- Unlike the previous method, which accepted only a single file, the new method can accept an array of files.
- Instead of InjectDetails, you now pass a ScriptInjection object. There are several differences between the two. For instance, the tabId is now passed as a member of
ScriptInjection.target
rather than as a method argument.
The following example illustrates this:
Manifest V2
async function getCurrentTab() {/* ... */}
let tab = await getCurrentTab();
chrome.tabs.executeScript(
tab.id,
{
file: 'content-script.js'
}
);
In a background script file.
Manifest V3
async function getCurrentTab()
let tab = await getCurrentTab();
chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['content-script.js']
});
In the extension service worker.
Replace tabs.insertCSS() and tabs.removeCSS() with scripting.insertCSS() and scripting.removeCSS()
In Manifest V3, the insertCSS()
and removeCSS()
methods transition from the tabs API to the scripting API. This transition requires adjustments to permissions in the manifest file as well as code modifications:
- The “scripting” permission.
- Either host permissions or the “activeTab” permission.
The functions in the scripting API operate similarly to their counterparts in the tabs API, with a few differences:
- When calling these methods, you now pass a CSSInjection object instead of InjectDetails.
- The tabId is now passed as a member of
CSSInjection.target
instead of as a method argument.
The following example illustrates how to use insertCSS()
. The process for removeCSS()
is identical.
Manifest V2
chrome.tabs.insertCSS(tabId, injectDetails, () => {
// callback code
});
In a background script file.
Manifest V3
const insertPromise = await chrome.scripting.insertCSS({
files: ["style.css"],
target: { tabId: tab.id }
});
// Remaining code.
In the extension service worker.
Replace Browser Actions and Page Actions with Actions
Browser actions and page actions, which were distinct concepts in Manifest V2, have become more similar over time. In Manifest V3, these concepts are merged into the Action API. This requires adjustments in both your manifest.json
file and your extension code compared to what you would have used in your Manifest V2 background script.
In Manifest V3, actions closely resemble browser actions, but the Action API does not provide hide()
and show()
methods like pageAction
did. If you still require page actions functionality, you can either emulate them using declarative content or use enable()
or disable()
methods with a tab ID.
To replace “browser_action
” and “page_action
” with “action” in the manifest.json:
Manifest V2
{
...
"page_action": { ... },
"browser_action": {
"default_popup": "popup.html"
}
...
}
Manifest V3
{
...
"action": {
"default_popup": "popup.html"
}
...
}
Replace the browserAction and pageAction APIs with the action API
Where your Manifest V2 utilized the browserAction
and pageAction APIs, you should now transition to using the action API.
Manifest V2
chrome.browserAction.onClicked.addListener(tab => { ... });
chrome.pageAction.onClicked.addListener(tab => { ... });
Manifest V3
chrome.action.onClicked.addListener(tab => { ... });
Replace callbacks with promises
In Manifest V3, many extension API methods now return promises. A Promise represents the eventual completion or failure of an asynchronous operation and allows you to handle the result when it’s ready. If you’re unfamiliar with Promises, you can learn more about them on MDN. This guide will explain how to use them in your Chrome extension.
For backward compatibility, some methods still support callbacks even after promise support is introduced. However, you cannot use both callbacks and promises in the same function call. If you provide a callback, the function won’t return a promise, and if you want a promise returned, you shouldn’t pass a callback. Certain API functionalities, such as event listeners, will still require callbacks. You can determine whether a method supports promises by checking for the “Promise” label in its API reference.
To convert from a callback to a promise, simply remove the callback and handle the returned promise. The example below, taken from the optional permissions sample (newtab.js), demonstrates how to convert from a callback to a promise. Note that the promise version could be further simplified using async/await syntax.
Callback
chrome.permissions.request(newPerms, (granted) => {
if (granted) {
console.log('granted');
} else {
console.log('not granted');
}
});
Promise
const newPerms = { permissions: ['topSites'] };
chrome.permissions.request(newPerms)
.then((granted) => {
if (granted) {
console.log('granted');
} else {
console.log('not granted');
}
});
Replace functions expecting a Manifest V2 background context
In Manifest V3, other extension contexts can only interact with extension service workers using message passing. Therefore, you’ll need to replace calls that expect a background context, such as:
- chrome.runtime.getBackgroundPage()
- chrome.extension.getBackgroundPage()
- chrome.extension.getExtensionTabs()
Your extension scripts should use message passing to communicate between a service worker and other parts of your extension. This currently involves using sendMessage()
and implementing chrome.runtime.onMessage
in your extension service worker. In the long term, you should plan to replace these calls with postMessage()
and a service worker’s message event handler.
Replace unsupported APIs
The methods and properties listed below need to be updated for Manifest V3.
Manifest V2 method or property | Replace with |
---|---|
Manifest V2 API | Manifest V3 API |
chrome.extension.connect() | chrome.runtime.connect() |
chrome.extension.connectNative() | chrome.runtime.connectNative() |
chrome.extension.getExtensionTabs() | chrome.extension.getViews() |
chrome.extension.getURL() | chrome.runtime.getURL() |
chrome.extension.lastError | Where methods return promises, use promise.catch() |
chrome.extension.onConnect | chrome.runtime.onConnect |
chrome.extension.onConnectExternal | chrome.runtime.onConnectExternal |
chrome.extension.onMessage | chrome.runtime.onMessage |
chrome.extension.onRequest | chrome.runtime.onMessage |
chrome.extension.onRequestExternal | chrome.runtime.onMessageExternal |
chrome.extension.sendMessage() | chrome.runtime.sendMessage() |
chrome.extension.sendNativeMessage() | chrome.runtime.sendNativeMessage() |
chrome.extension.sendRequest() | chrome.runtime.sendMessage() |
chrome.runtime.onSuspend (background scripts) | Not supported in extension service workers. Use the beforeunload document event instead. |
chrome.tabs.getAllInWindow() | chrome.tabs.query() |
chrome.tabs.getSelected() | chrome.tabs.query() |
chrome.tabs.onActiveChanged | chrome.tabs.onActivated |
chrome.tabs.onHighlightChanged | chrome.tabs.onHighlighted |
chrome.tabs.onSelectionChanged | chrome.tabs.onActivated |
chrome.tabs.sendRequest() | chrome.runtime.sendMessage() |
chrome.tabs.Tab.selected | chrome.tabs.Tab.highlighted |
Continue reading:

Frequently Asked Questions (FAQ)
What are the main changes introduced in Manifest V3 compared to V2?
Manifest V3 introduces several significant changes, including a transition to service workers for background tasks, new permissions structures, stricter security policies, and the use of promises instead of callbacks in many APIs. These changes enhance performance, security, and ease of use for developers.
How do I update the “executeScript()” method from tabs API to scripting API?
In Manifest V3, the executeScript()
method moves to the scripting API. You need to add “scripting” permission in the manifest and update the code to use the scripting.executeScript()
method, which now accepts an array of files and requires passing a ScriptInjection object.
How do I handle CSS injection in Manifest V3?
CSS injection methods insertCSS()
and removeCSS()
have moved to the scripting API in Manifest V3. You need to update your permissions and use the scripting.insertCSS()
and scripting.removeCSS()
methods, passing a CSSInjection object.
What changes do I need to make for browser actions and page actions?
In Manifest V3, browser actions and page actions are merged into the Action API. Update your manifest to use “action” instead of “browser_action” or “page_action” and modify your code to use the chrome.action
API.
How do I transition from callbacks to promises?
Many API methods in Manifest V3 return promises. You should remove callbacks and handle the returned promise using .then()
or async/await
syntax. This change simplifies asynchronous code and improves readability.
What should I do with functions that expect a background context?
In Manifest V3, background tasks are handled by service workers, and you must use message passing for communication. Replace calls to chrome.runtime.getBackgroundPage()
and similar functions with sendMessage()
and implement message handlers in your service worker.
How do I update the manifest file for host permissions?
Host permissions are specified separately in Manifest V3. Move host permissions out of “permissions” and “optional_permissions” sections into “host_permissions” and “optional_host_permissions”
What changes are required for web accessible resources?
In Manifest V3, web accessible resources are defined more selectively to enhance security. Instead of listing files, provide an array of objects that map resources to specific URLs or extension IDs.
Are there any tools or resources to help with the migration?
Yes, Google provides extensive documentation, guides, and sample extensions to help developers migrate from Manifest V2 to V3. The Chrome Extension Samples repo is a useful resource.
What is the timeline for migrating to Manifest V3?
While Manifest V3 support is available in Chrome 88 and later, Google has provided timelines for the deprecation of Manifest V2. Check the official Chrome developer documentation for the latest updates on these timelines.