Introduction to browser extensions
Browser extensions power everything from ad blockers to accessibility tools and developer utilities. If you're building one, you'll quickly discover that a Chrome and a Firefox extension are similar — but not identical.
Both ecosystems are based on the WebExtensions standard, yet they differ in APIs, permissions, architecture, and messaging behavior. This guide explains the real differences, the browser-specific APIs, and how message passing works in each.
The Common Ground: WebExtensions
Google Chrome and Mozilla Firefox both implement the WebExtensions API, which was originally designed to allow cross-browser compatibility.
Core concepts shared by both:
manifest.json- Background scripts / service workers
- Content scripts
- Extension pages (devtools, popup, options)
- Permissions system
- Messaging and Storage APIs
In many cases, the same codebase works with minimal changes. But under the hood, important differences exist.
Manifest Differences (MV2 vs MV3)
Chrome
Chrome has moved to Manifest V3 (MV3) as the standard.
- Background scripts replaced by service workers
- Stronger security model
- No persistent background pages
- Declarative APIs favored over dynamic ones
- Stricter remote code restrictions
Firefox
Firefox still allows persistent backgrounds in Manifest V2 (MV2) extensions, which many developers find easier. It supports:
- Manifest V2 (still supported)
- Manifest V3 (supported but not fully identical to Chrome)
Background Architecture
Chrome
- Uses service workers
- Non-persistent (can shut down anytime)
- Event-driven
- No direct DOM access
- Must re-initialize state frequently
Firefox
- Supports persistent background scripts (MV2)
- Easier long-running tasks
- More predictable lifecycle
- MV3 service worker support exists but differs slightly
Browser API Differences
Chrome's API: Uses chrome.*
- Callback-based (MV2)
- Promise support added gradually in MV3
chrome.tabs.query({ active: true }, (tabs) => {
console.log(tabs);
});Firefox's API: Uses browser.*
- Promise-based
- Modern async style
browser.tabs.query({ active: true }).then((tabs) => {
console.log(tabs);
});Cross-Browser Compatibility Trick
Firefox provides a polyfill so Chrome-style code can work:
if (typeof browser === "undefined") {
var browser = chrome;
}Or use Mozilla's official WebExtension polyfill.
Messaging Flow in Extensions
Extensions consist of isolated environments:
- Background script
- Content script
- Popup / UI pages
- DevTools pages (optional)
These cannot directly access each other's variables — messaging is required. Both browsers isolate extension scripts from the webpage context.
Communication flow
Direct communication between webpage and extension requires:
window.postMessage- DOM events
- Injected scripts
This behavior is consistent across Chrome and Firefox — but the message routing expectations differ in practice.
Firefox
In Firefox extensions, messaging often needs to follow the explicit chain:

Sending messages directly from the popup to the content script may fail or behave inconsistently unless the correct tab context and permissions are used. The background script acts as a reliable central hub for routing messages.
Chrome
As compared to Firefox, Chrome can be considered as more lenient.

A popup can frequently send messages directly to a content script (for example, using chrome.tabs.sendMessage) and it will still work as expected, provided the content script is injected in the active tab.
Final Thoughts
Chrome and Firefox extensions share a common foundation, but subtle architectural differences — especially around APIs, permissions, lifecycle, and messaging behavior — can significantly impact development.
If you understand:
- Manifest differences
- API styles (
chromevsbrowser) - Background lifecycle
- Message flow patterns
…you can build robust cross-browser extensions with confidence.