Firefox Extension Manifest V3 vs V2: What Actually Changed
If you've been following Chrome's controversial Manifest V3 migration, you might be wondering: does Firefox have the same MV3? The answer is: yes and no. Firefox has its own implementation of Manifest V3, and it's meaningfully different from Chrome's.
The Short Version
- Firefox MV3 supports both MV2 and MV3 extensions
- Firefox's MV3 is less restrictive than Chrome's
- Firefox still supports
browser.webRequestblocking in MV3 - Service workers replace background pages (same as Chrome)
What Changed: Background Scripts
MV2 (background page):
// manifest.json
{
"manifest_version": 2,
"background": {
"scripts": ["background.js"],
"persistent": false
}
}
MV3 (service worker):
// manifest.json
{
"manifest_version": 3,
"background": {
"service_worker": "background.js"
}
}
The key implication: service workers don't persist state in memory. They're event-driven and can be terminated at any time. Code like this breaks:
// ❌ This DOESN'T work in MV3 service workers
let cachedData = {}; // Lost when service worker terminates!
chrome.tabs.onActivated.addListener(() => {
if (cachedData.weather) {
// cachedData might be {} if SW was terminated
}
});
Fix: Use browser.storage.session (MV3 only) or browser.storage.local:
// ✅ This works in MV3
async function getCachedData() {
const { cachedData } = await browser.storage.session.get('cachedData');
return cachedData || {};
}
async function setCachedData(data) {
await browser.storage.session.set({ cachedData: data });
}
What Changed: Content Security Policy
MV2: More permissive CSP
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
MV3: Stricter — no unsafe-eval, no remote scripts
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
}
This means: no more eval(), no more loading scripts from external CDNs directly. You must bundle all JavaScript.
What Changed: Action API
MV2: Separate browser_action and page_action
MV3: Unified action
// MV2
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
}
// MV3
"action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
}
In code:
// MV2
browser.browserAction.setBadgeText({ text: '5' });
// MV3
browser.action.setBadgeText({ text: '5' });
What Firefox Kept (That Chrome Removed)
This is the important difference: Firefox MV3 still allows blocking webRequest.
Chrome replaced blocking webRequest with declarativeNetRequest, which is less powerful but more privacy-preserving. Firefox offers both:
// Firefox MV3 — still works!
browser.webRequest.onBeforeRequest.addListener(
(details) => {
if (details.url.includes('tracker.example.com')) {
return { cancel: true };
}
},
{ urls: ['<all_urls>'] },
['blocking']
);
This matters for ad blockers and privacy tools — uBlock Origin works on Firefox MV3 because of this.
Migrating a New Tab Extension
For new tab extensions like Weather & Clock Dashboard, the migration is mostly straightforward:
// Before (MV2)
{
"manifest_version": 2,
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {},
"chrome_url_overrides": {
"newtab": "newtab.html"
}
}
// After (MV3)
{
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module" // Optional: enables ES modules
},
"action": {},
"chrome_url_overrides": {
"newtab": "newtab.html"
}
}
The newtab override itself doesn't change between MV2 and MV3 — that part is stable.
Should You Migrate?
For Firefox: MV2 still works and Mozilla has said they'll support it. Migrating to MV3 is optional for now.
For Chrome: MV2 is being deprecated. If you also publish to the Chrome Web Store, migrating is necessary.
For new extensions: Start with MV3. It's the future-proof choice.
For the Weather & Clock Dashboard, I'm currently on MV2 since the extension is Firefox-only. Migration to MV3 is on the roadmap.
Install the extension: Weather & Clock Dashboard on AMO
Questions about MV3 migration? Drop them in the comments!
Part of a series on building Firefox browser extensions.
Top comments (0)