Highlights

  • The Google Chrome team announced the deprecation plan for manifest V2.
  • The transition was initially paused due to migration issues from manifest V2 to V3.
  • In November 2023, the Chrome team released a new timeline.
  • All extensions should migrate to manifest V3 by June 2024.
  • Coditude is providing a free service to assist with the migration from manifest V2 to V3.
  • Major browsers like Firefox and Edge are also transitioning to manifest V3.
  • As of now, Safari has not announced any plans regarding manifest V3 but most likely they will follow.

Recently, Google announced that Manifest V2 is going to be phased out progressively and extensions should be migrated to Manifest V3.

Experiments to Turn off MV2 in all Channels Including Stable

Experience Hassle-Free Chrome Extension Migration from v2 to v3 with Coditude – It's Free!

Chief Executive Officer

Hrishikesh Kale

Chief Executive Officer

Chief Executive OfficerLinkedin

30 mins FREE consultation

Today, we're going to learn how to convert a Chrome extension from Manifest V2 to V3 to lay the foundation for a modern browser extension.

But Why Migrate To Manifest V3?

As Chrome's Documentation says :

Migrate to Manifest V3

Here's the Migration checklist:

Changing The Version

The first step is that you need to replace the version of your Manifest. In your manifest.json file, change it as follows:

//manifest.json
{
...,
"manifest_version": 3
...,
}

Host Permissions

Do you have host permissions in your permissions or optional_permissions arrays in manifest.json? Move host permissions into the separate field named host_permissions

Here's our V2 manifest, where host permissions were clubbed inside the permissions field, which would also hold other Chrome API permissions like storage if we were using any:

{
"version": "0.1",
"name": "News Reader",
"description": "",
"permissions": [
"tabs",
"bookmarks",
"storage",
"https://www.coditude.com/",
],
"content_scripts": [{
"js": ["js/content.js"],
"matches": [":///"]
}],
"manifest_version": 2
}

Here's our V3 manifest, with our host permissions request moved into the new host_permissions field:

{
"version": "0.1",
"name": "News Reader",
"description": "",
"permissions": [
"tabs",
"bookmarks",
"storage",

],
"content_scripts": [{
"js": ["js/content.js"],
"matches": ["*://*/*"]
}],
"host_permissions": [
"https://www.coditude.com/",
"*://*/*"
],
"manifest_version": 3
}

Background Scripts

Background pages and background scripts are deprecated in Manifest V3. They are replaced by service workers. We will also need to remove the persistent key. This means that the background field of the manifest file needs to be modified in this way.

Here's our V2 manifest with background:

{
...,
"background": {
"scripts": ["assets/js/bg1.js","assets/js/bg2.js"],
"persistent": true
},
...
}

And here's our V3 manifest with the service worker, which is just a string containing a single.js file.

{
...,
"background": {
"service_worker": "assets/js/bg.js"
},
...
}

Although you can include multiple files and call them from service-worker.js, a simpler way is to use the importScripts() method at the top of the service-worker.js script like this

// service-worker.js
importScripts('script1.js', 'script2.js');
//code

Actions

In the earlier versions, Actions used to be divided into browser_action and page_action. In Manifest V3, they are unified into a single field named Action. This is done to reduce confusion between those two fields as they ultimately serve a common purpose.

Here's our V2 manifest:

{
...,
"browser_action": {
"default_title": "popup content",
"default_popup": "html/popup.html"
},
"page_action": {
"default_icon": {
"16": "images/icon16.png",
...
},
"default_title": "A Popup",
"default_popup": "popup.html"
}
...
}

Here's our V3 manifest:

{
"action": {
"default_popup": "html/popup.html",
"default_title": "popup content",
"default_icon": {
"16": "images/logo-16.png",
...
}
},
"icons": {
"16": "images/logo-16.png",
...
}
}}

Content Security Policy

Content Security Policy is yet another layer of security for your application. It lets you define all the origins from which you are loading any styles, scripts, or data. For each of the styles, scripts, fonts, or connect, you need to specify the domains which you will be loading in your application. If you are using a Vue CDN or another domain, you need to specify this domain in the Content Security Policy.

In Manifest V2, we specify content_security_policy as a string like this:

"content_security_policy": "script-src 'self' 'unsafe-eval' https://cdn.jsdelivr.net; object-src 'self'"

In Manifest V3, sandbox is used to treat the page as though it were loaded into an iframe with the sandbox attribute. This will effectively remove this frame from associating with the main application in terms of the Same Origin Policy. This will prevent the page from performing certain actions, such as submitting forms.

//manifest 3
{
...,
"content_security_policy": {
"extension_pages": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"sandbox": {
"pages": [
"page1.html",
"directory/page2.html"
]
}
},
...
}

Web-Accessible Resources

To declare the packed resources that need to be accessed from the web, the web_accessible_resources will no longer need a list of files. Instead, you can now provide a list of objects, each of which map to a set of resources such as a set of URLs or extension IDs:

In Manifest V2, we provide a list of files

"web_accessible_resources": [
"js/options.js",
"js/main.js",
"js/injected.js"
]

However, since scripts from external domains are not allowed in MV3, all scripts must be included in the extension package, which in turn, are served from the chrome-extension:// protocol. For instance, a page in your extension is chrome-extension:///onboarding.html.

In Manifest V3, each object should be in this structure:

"web_accessible_resources": [{
"resources": ["js/options.js","js/main.js","js/injected.js"],
"matches": [<urls>],
"extension_ids": [<keys>],
}]

ExecuteScript()

The executeScript() method moves from chrome.tabs.executeScript() to chrome.scripting.executeScript().You can no longer execute a string; instead, you need to move your code into a static JavaScript file or a function, then execute it using the executeScript method's property, like this :

in Manifest V2, we do this :

// background.js
chrome.tabs.executeScript({
code: 'alert("script working!")'
});

In Manifest V3, it should be like this :

// background.js
chrome.scripting.executeScript({
file: 'content-script.js'
});

// content-script.js
alert("test!");

//or just place the code into a function
// background.js
function showAlert() {
alert("test!");
}

chrome.scripting.executeScript({
function: showAlert
});

Refactor Chrome API'S To Use Promises

The Chrome team has added Promise support for many APIs, so we can finally use Promises in some of the Chrome APIs! Note that Callbacks are still supported, so you don't need to refactor all your code right away. This is one thing which we all are looking forward to using. Let us see some examples.

// Using callback
chrome.action.setBadgeBackgroundColor({ color: '#FFF' }, () => {
chrome.action.setBadgeText({ text: 'it works' });
});

// Using promises
await chrome.action.setBadgeBackgroundColor({ color: '#FFFF' });
await chrome.action.setBadgeText({ text: 'it works' });

// Using callback
chrome.tabs.query(query, (tabs) => {
// callback logic
});

// Using promises
const tabs = await chrome.tabs.query(query);

Network Requests Modification Using DeclarativeNetRequest

declarativeNetRequest is a replacement for webRequest API. This API is used to block or modify network requests by specifying declarative rules. This lets extensions modify network requests without intercepting them or viewing their content. It helps provide enhanced privacy to users because extensions can't actually read the network requests made on the user's behalf.

{
"name": "My extension",
...

"declarative_net_request": {
"rule_resources": [{
"id": "ruleset_1",
"enabled": true,
"path": "rules_1.json"
}, {
"id": "ruleset_2",
"enabled": false,
"path": "rules_2.json"
}]
},
"permissions": [
"declarativeNetRequest",
"declarativeNetRequestFeedback",
"*://nytimes.com/*"
],
...
}

Each ruleset consists of rules and a single declarative Rule consists of four fields: id, priority, condition, and action. Let us see an example.

{
"id" : 1,
"priority": 1,
"action" : { "type" : "block" },
"condition" : {
"urlFilter" : "football",
"domains" : ["nytimes.com"],
"resourceTypes" : ["script"]
}
}

The above rule will block all script requests originating from nytimes.com to any URL containing football as a substring. The urlFilter field of a rule condition is being used to specify the pattern which is matched against the request URL.

Congratulations!

We have successfully migrated the extension from Manifest V2 to V3.

Coditude has over a decade of experience developing Browser Extensions. If you have any questions or queries about building/converting an extension, our dedicated Chrome extension, and plugin development team will be happy to answer them for you. Just drop us a line at browser extension services.

Experience Hassle-Free Chrome Extension Migration from v2 to v3 with Coditude – It's Free!

Chief Executive Officer

Hrishikesh Kale

Chief Executive Officer

Chief Executive OfficerLinkedin

30 mins FREE consultation