Chrome Extension - Tips For Migrating To Manifest V3

Shadow Maven
5 min readMar 19, 2023

Introduction

As we approach June 2023, the deadline for Chrome Web Store to cease support for Manifest V2 extensions with Public visibility looms large. Extensions that fail to migrate to Manifest V3 will see their visibility changed to Unlisted.

While Manifest v3 has been available since the release of Chrome 88 earlier this year, there are still extensions that haven’t been migrated to use it. If you’re one of the many developers still grappling with this migration, you’re not alone. In this article, I’ll share some valuable tips and lessons I learned while migrating my own Chrome extension from Manifest V2 to V3.

The Road to Manifest V3: Challenges and Solutions

In this article, instead of outlining every step needed to complete your migration, I will only talk about the main challenges I faced and the solutions I found.

Adapting to Service Workers

One of the most significant changes in Manifest V3 is the transition from background pages to service workers for background tasks. This shift required modifications across my extension, with one of the challenges being the need to persist states properly and reload them when the service worker becomes active again. By using local storage to save and load the extension’s state, I could effectively maintain functionality despite the service worker being killed and resurrected multiple times by Chrome.

For example, if some parts of your background page were relying on global variables like in the code below to maintain the state, they will no longer work, since the background service worker will be killed and resurrected multiple times.

let globalState = {
foo: false,
bar: false
};

function updateState(newState): void {
globalState = newState;
}

Here’s an example of how you can persist the state whenever it changes or when chrome decides to unload the background service worker.


// Initialize your extension's state
let state = {
foo: false,
bar: false
};

//save the extension's state to local storage
function saveState(): void {
localStorage.setItem('state', JSON.stringify(state));
}

// Function to load the extension's state from local storage
function loadState(): void {
const savedState = localStorage.getItem('state');
if (savedState) {
state = JSON.parse(savedState);
}
}

// Save the extension's state whenever it changes
function updateState(newState: PersistedState): void {
state = newState;
saveState();
}

// Initialize the extension's state on startup
loadState();

Navigating Limitations Of executeScript And Alternatives

If you were able to exploit the script injection methods allowed in Manifest V2, you might run into some challenges while upgrading to use the new APIs provided in manifest V3.

For instance, if your extension needs to have permission to execute scripts on the active tab that the user is on, you will have to start using the activeTab permission in your manifest. And it’s not as straightforward as things were in manifest V3.

// manifest.json
{
"permissions": ["activeTab"]
}

The “activeTab” permission is a proper alternative to using “<all_urls>” in manifest V2 but it only lets an extension access the currently active tab temporarily when the user clicks on the extension’s icon or from a context menu. This means you can no longer execute scripts when users trigger your extension through other means, like custom events.

Embracing declarativeNetRequest

Manifest V3 introduces the declarativeNetRequest API to modify network requests securely, replacing the older webRequest API. By specifying rules in a JSON file, you can easily block, redirect, or modify requests without complex JavaScript code.

Additionally, the declarativeNetRequestWithHostAccess API allows extensions to access a whitelist of domains exempt from these rules, providing greater control over network requests.

Here’s an example of how you can use the declarativeNetRequest and declarativeNetRequestWithHostAccess

// Declare a set of rules to block network requests
const rules = [
{
id: 'block-google-analytics',
priority: 1,
action: {
type: 'block'
},
condition: {
urlFilter: 'https://www.google-analytics.com/*'
}
}
];

// Declare a set of host permissions to exempt from the rules
const hostPermissions = ['https://www.example.com/*'];

// Add the rules to the extension's manifest
chrome.runtime.onInstalled.addListener(() => {
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [],
addRules: rules
}, () => {
console.log('Added dynamic rules.');
});
});

// Add the host permissions to the extension's manifest
chrome.runtime.onInstalled.addListener(() => {
chrome.declarativeNetRequestWithHostAccess.setHosts({
hosts: hostPermissions
}, () => {
console.log('Added dynamic hosts.');
});
});

In the code above, we declare a set of rules to block network requests made to Google Analytics and a set of host permissions to be exempt from the rules. We then add the rules and host permissions to the extension’s manifest using the chrome.declarativeNetRequest.updateDynamicRules() and chrome.declarativeNetRequestWithHostAccess.setHosts() methods respectively.

Note that the rules and host permissions are added in the onInstalled event listener, which is triggered when the extension is installed or updated.

For more information on why and how to migrate to the new declarativeNetRequest, refer to this blog post

Utilizing Promise Support and Callback Alternatives

Promise support in Manifest V3 simplifies handling asynchronous actions, reducing the need for nested callbacks. However, for developers who prefer callbacks or have existing callback-based code, Manifest V3 continues to support callback alternatives alongside promises.

Note: Promise support is not yet available for all APIs in manifest V3, So to check whether a method supports promises when you’re using manifest V3, look for the “Promise” label in its API reference. You’ll find it below the method signature. Here’s an example from the chrome.tabs namespace:

API supporting promise

Example demonstrating the usage of promise.

chrome.runtime.onInstalled.addListener(async () => {
try {
const returnValue= await chrome.tabs.captureVisibleTab(
1234, // window ID
null,// options
)
console.log(`Return value in sync mode: ${JSON.stringify(returnValue)}`);
} catch (error) {
console.error(error);
}
});

Conclusion: Manifesting Success in Manifest V3

As the deadline for Manifest V2 support draws nearer, the pressure to migrate to Manifest V3 is undeniable. By sharing my own challenges in specific areas of migration and solutions, I hope to ease the transition for fellow developers who might face similar challenges as I did.

Let me know if you’re facing any unique challenges with your migration in the comments.

--

--

Shadow Maven

Developer | Technology Enthusiast | Optimistic Nihilist