DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

Firefox Extension Manifest V3 Migration: What Actually Changed

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 action key
  • browser_action / page_action → deprecated, use action

What Actually Changed in manifest.json

Version declaration

// MV2
{
  "manifest_version": 2
}

// MV3
{
  "manifest_version": 3
}
Enter fullscreen mode Exit fullscreen mode

Action key

// MV2
{
  "browser_action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  }
}

// MV3
{
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  }
}
Enter fullscreen mode Exit fullscreen mode

Background scripts → service workers

// MV2
{
  "background": {
    "scripts": ["background.js"]
  }
}

// MV3
{
  "background": {
    "service_worker": "background.js"
  }
}
Enter fullscreen mode Exit fullscreen mode

Host permissions moved out of permissions

// MV2
{
  "permissions": ["https://api.example.com/*", "storage", "tabs"]
}

// MV3
{
  "permissions": ["storage", "tabs"],
  "host_permissions": ["https://api.example.com/*"]
}
Enter fullscreen mode Exit fullscreen mode

Service Worker Gotchas

The biggest behavioral change is background scripts becoming service workers. Service workers:

  1. Don't have access to the DOM
  2. Get terminated when idle (no persistent state in memory)
  3. Must use self instead of window
// 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 });
  }
});
Enter fullscreen mode Exit fullscreen mode

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
  }
});
Enter fullscreen mode Exit fullscreen mode

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'"
  }
}
Enter fullscreen mode Exit fullscreen mode

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 (or browser_url_overrides) still works
  • browser.storage API unchanged
  • browser.tabs API 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"]
}
Enter fullscreen mode Exit fullscreen mode

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

  1. Firefox MV3 keeps blocking WebRequest — huge win vs Chrome
  2. Background → service worker is the biggest behavioral change
  3. For new tab extensions, migration is usually 2-3 line changes
  4. CSP is stricter — watch out for eval() usage in dependencies
  5. 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)