Firefox Extension Manifest V3 Migration: What Actually Changed
Manifest V3 (MV3) has been a contentious topic in browser extension development — mostly because of Chrome's aggressive changes to blocking WebRequest API. Firefox's approach is different and more developer-friendly. Here's what actually changed and what you need to know.
The Short Version for Firefox
Firefox's MV3 implementation is more permissive than Chrome's:
- Blocking WebRequest still works (unlike Chrome, which deprecated it)
- Background pages → service workers (same as Chrome)
-
Browser action → new unified
actionkey -
browser_action/page_action→ deprecated, useaction
What Actually Changed in manifest.json
Version declaration
// MV2
{
"manifest_version": 2
}
// MV3
{
"manifest_version": 3
}
Action key
// MV2
{
"browser_action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
}
}
// MV3
{
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
}
}
Background scripts → service workers
// MV2
{
"background": {
"scripts": ["background.js"]
}
}
// MV3
{
"background": {
"service_worker": "background.js"
}
}
Host permissions moved out of permissions
// MV2
{
"permissions": ["https://api.example.com/*", "storage", "tabs"]
}
// MV3
{
"permissions": ["storage", "tabs"],
"host_permissions": ["https://api.example.com/*"]
}
Service Worker Gotchas
The biggest behavioral change is background scripts becoming service workers. Service workers:
- Don't have access to the DOM
- Get terminated when idle (no persistent state in memory)
-
Must use
selfinstead ofwindow
// MV2 background script — this works
window.myGlobalState = {};
setInterval(() => console.log('still alive'), 1000);
// MV3 service worker — this breaks
// window is not defined in service worker scope
// State is lost when the service worker stops
// Instead, persist state to storage
self.addEventListener('message', async (event) => {
const { type, data } = event.data;
if (type === 'SAVE_STATE') {
await browser.storage.session.set({ state: data });
}
});
Keeping a service worker alive
For new tab extensions, this is less of an issue since the new tab page itself is a foreground page with a normal DOM context. But if you need the background script:
// Use alarms to keep the service worker alive
browser.alarms.create('keepAlive', { periodInMinutes: 0.4 });
browser.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'keepAlive') {
// Service worker is alive
}
});
Content Security Policy Changes
MV3 tightened CSP:
// MV2 — this was allowed
{
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}
// MV3 — unsafe-eval is NOT allowed in extension pages
{
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
}
}
This breaks libraries that use eval() — notably some templating engines and older versions of Angular.
What Stayed the Same
For simple extensions like new tab pages, most things don't change:
-
chrome_url_overrides(orbrowser_url_overrides) still works -
browser.storageAPI unchanged -
browser.tabsAPI unchanged - Content scripts work the same way
- The extension popup works the same way
Real-World Migration Example
Here's the before/after for Weather & Clock Dashboard:
// Before (MV2)
{
"manifest_version": 2,
"browser_action": {},
"permissions": ["storage"]
}
// After (MV3)
{
"manifest_version": 3,
"action": {},
"permissions": ["storage"]
}
For this extension (pure new tab page, no background script, no remote code), the migration was literally two line changes.
Should You Migrate?
For Firefox extensions published to AMO:
- MV3 is not yet required — AMO still accepts MV2
- MV3 is recommended for new extensions
- Chrome requires MV3 if you want to publish there
If you're building Firefox-only and it's working on MV2, there's no urgent need to migrate. But MV3 is the future, and Firefox's implementation is sane — so for new extensions, start with MV3.
Key Takeaways
- Firefox MV3 keeps blocking WebRequest — huge win vs Chrome
- Background → service worker is the biggest behavioral change
- For new tab extensions, migration is usually 2-3 line changes
- CSP is stricter — watch out for
eval()usage in dependencies - Host permissions are now separate from other permissions
Weather & Clock Dashboard is a free Firefox extension built with MV3 — live weather, world clocks, search bar. No tracking, MIT licensed.
Top comments (0)