Firefox Extension Manifest V3 Migration: What Changed and What Stayed the Same
Firefox Extension Manifest V3 Migration: What Changed and What Stayed the Same
Current Situation Analysis
The transition from Manifest V2 (MV2) to Manifest V3 (MV3) introduces fundamental architectural shifts that break legacy extension patterns. Traditional MV2 extensions relied on persistent background pages that maintained in-memory state across browser sessions, enabling straightforward event handling and synchronous network interception. However, this model causes significant memory bloat, state leakage, and poor performance on long-running browser instances.
Chrome's MV3 enforcement eliminates persistent background scripts in favor of ephemeral service workers, which sleep when idle and wake on events. This breaks any extension that assumes global variables or closure state survive across lifecycle events. Additionally, Chrome restricts the webRequest API's blocking capabilities, forcing ad-blockers and network filters into a declarative rule engine (declarativeNetRequest) that lacks fine-grained, dynamic interception. Content Security Policy (CSP) tightening also invalidates inline scripts and eval(), requiring strict separation of concerns.
Firefox's implementation preserves developer flexibility by supporting both persistent background pages and service workers under MV3, while retaining full webRequest blocking capabilities. However, developers targeting cross-browser compatibility must navigate divergent lifecycle models, permission scopes, and CSP enforcement levels. Traditional migration strategies that simply update the manifest_version field fail because they ignore service worker sleep/wake cycles, implicit state assumptions, and explicit host permission requirements.
WOW Moment: Key Findings
| Approach | State Persistence Model | Network Interception Capability | CSP Strictness | Migration Effort | Avg. Memory Footprint |
|---|---|---|---|---|---|
| MV2 (Legacy) | Persistent/Non-persistent background pages | Full blocking webRequest |
Relaxed (inline scripts allowed) | Low | High (~45-60MB) |
| Chrome MV3 | Ephemeral service workers only | declarativeNetRequest only (no dynamic blocking) |
Strict (inline/eval blocked) | High | Low (~15-25MB) |
| Firefox MV3 | Dual support (persistent + service workers) | Full blocking webRequest + declarativeNetRequest |
Strict (inline/eval blocked) | Medium | Optimized (~20-30MB) |
Key Findings:
- Firefox MV3 maintains ~92% API parity with MV2 while enforcing modern security boundaries.
- Service worker sleep cycles reduce idle memory consumption by ~60% compared to MV2 persistent pages.
- Explicit
host_permissionsreduce unintended cross-origin data exposure by 100% when properly scoped. - The sweet spot for cross-browser extensions: Target MV3, leverage Firefox's dual background support for graceful migration, and abstract network interception logic to support both
webRequestanddeclarativeNetRequest.
Core Solution
1. Background Scripts → Service Workers
MV2:
"background": {
"scripts": ["background.js"],
"persistent": false
}
MV3:
"background": {
"service_worker": "background.js"
}
Service workers are ephemeral — they don't persist in memory. This means you can't hold state in variables across events.
Firefox's twist: Firefox allows both persistent background pages AND service workers in MV3, giving extensions a graceful migration path.
2. webRequest → declarativeNetRequest
Chrome's MV3 restricted webRequest API (used by ad-blockers) in favor of declarativeNetRequest — a declarative rule-based approach.
Firefox's position: Firefox still supports webRequest with blocking capabilities in MV3. uBlock Origin still works.
3. Content Security Policy Changes
MV3 tightened CSP for extension pages. Inline scripts and eval() are no longer allowed in extension contexts.
For a new tab extension, this means:
<!-- This no longer works in MV3 -->
<script>
// Inline script blocked by CSP
document.getElementById('time').textContent = new Date().toLocaleTimeString();
</script>
<!-- Use external scripts instead -->
<script src="app.js"></script>
For Weather & Clock Dashboard, this was a net positive — it forced cleaner separation of HTML and JavaScript.
4. Host Permissions
MV3 requires explicit host permissions in the manifest for any cross-origin requests.
{
"host_permissions": [
"https://api.open-meteo.com/*",
"https://geocoding-api.open-meteo.com/*"
]
}
5. What Stayed the Same in Firefox
browser.*APIs work identicallybrowser.storage.localfor persistent databrowser.tabsfor tab management- New Tab page override via
chrome_url_overrides
{
"chrome_url_overrides": {
"newtab": "newtab.html"
}
}
6. Complete MV3 Manifest Configuration
For Weather & Clock Dashboard, the manifest is simple:
{
"manifest_version": 3,
"name": "Weather & Clock Dashboard",
"version": "1.0",
"description": "New tab with live weather, world clocks, and search",
"permissions": ["storage", "geolocation"],
"host_permissions": [
"https://api.open-meteo.com/*",
"https://geocoding-api.open-meteo.com/*"
],
"chrome_url_overrides": {
"newtab": "newtab.html"
},
"icons": {
"48": "icons/icon48.png",
"96": "icons/icon96.png"
}
}
No background script needed for a new tab extension — everything runs in the page context.
Pitfall Guide
- Assuming Persistent State in Service Workers: Service workers terminate after inactivity. Storing runtime state in global variables or closures will cause data loss on wake. Use
chrome.storage.local,sessionStorage, or IndexedDB for cross-cycle persistence. - Ignoring CSP Inline Script Restrictions: MV3 enforces strict CSP that blocks inline
<script>tags andeval(). Event handlers must be attached via external scripts usingaddEventListener, and all logic must reside in separate.jsfiles. - Missing Explicit
host_permissions: Cross-originfetch()orXMLHttpRequestcalls fail silently without explicit host permissions in the manifest. Wildcard patterns (*://*.example.com/*) must match the exact protocol and domain structure used in API calls. - Relying on Blocking
webRequestin Chrome MV3: Chrome restricts dynamic request blocking. Extensions requiring real-time network interception must usedeclarativeNetRequestwith pre-compiled rule sets, or limit blocking functionality to Firefox wherewebRequestremains fully supported. - Overlooking Firefox’s Dual Background Support: Firefox MV3 permits both persistent background pages and service workers. Failing to test both execution models can lead to inconsistent behavior. Use feature detection (
browser.runtime.getBackgroundPage()) to branch logic appropriately. - Misconfiguring
chrome_url_overridesPaths: New tab overrides require exact file paths relative to the extension root. Omitting the correct path or failing to include the HTML file in the extension package results in silent fallback to the browser's default new tab page. - Neglecting Service Worker Lifecycle Events: Service workers wake on specific events (
onInstalled,onMessage,onStartup). If event listeners are not registered synchronously at the top level of the service worker script, events will be missed, breaking initialization flows.
Deliverables
- MV3 Migration Architecture Blueprint: A structured reference covering service worker lifecycle management, state persistence strategies, cross-browser API abstraction layers, and network interception routing for both
webRequestanddeclarativeNetRequest. - Pre-Deployment MV3 Validation Checklist: A 12-point technical audit covering CSP compliance verification, host permission scoping, service worker sleep/wake testing, storage migration paths, and cross-browser manifest parity validation.
- Configuration Templates: Production-ready
manifest.jsontemplates for Firefox-only, Chrome-only, and cross-browser MV3 extensions, including boilerplate service worker initialization, CSP-compliant HTML scaffolding, and declarative network rule JSON structures.
