Firefox's WebExtensions API is powerful and compatible with Chrome extensions, but Firefox also has APIs Chrome doesn't support. If you're building extensions or customizing your browser, here's what's unique to Firefox.
The API Parity Status
Firefox supports the vast majority of Chrome WebExtensions APIs (Manifest V2 and V3). The goal has been to maintain compatibility so Chrome extensions can run on Firefox with minimal modification.
Key difference: Firefox still supports the blocking webRequest API in Manifest V2 extensions. Chrome removed this in favor of a non-blocking declarativeNetRequest API. This is why uBlock Origin in advanced mode works on Firefox but not Chrome.
Firefox-Only APIs
browser.tabs.captureTab
// Capture a screenshot of a specific tab
const imageData = await browser.tabs.captureTab(tabId, { format: 'png' });
More flexible than Chrome's chrome.tabs.captureVisibleTab.
browser.find
// Programmatic find-in-page
const result = await browser.find.find('search term', {
tabId: tab.id,
caseSensitive: false,
entireWord: false,
});
// result: { count, activeMatchOrdinal }
// Highlight results
await browser.find.highlightResults({ tabId: tab.id });
// Clear highlights
await browser.find.removeHighlighting(tabId);
No equivalent in Chrome's extension API.
browser.pkcs11
Access to PKCS#11 hardware security modules (smart cards, hardware tokens). Chrome extensions can't do this.
browser.theme
// Apply a custom theme to Firefox
await browser.theme.update({
colors: {
toolbar: '#1e1e1e',
toolbar_text: '#e0e0e0',
frame: '#111111',
}
});
// Get current theme
const currentTheme = await browser.theme.getCurrent();
Firefox allows extensions to dynamically modify the browser UI colors. This is more powerful than Chrome's limited theming API.
browser.experiments
Firefox Nightly allows extensions to use experimental APIs via browser.experiments. This is Firefox's mechanism for testing new extension APIs before standardization.
The browser vs chrome Namespace
Firefox extensions can use either:
-
browser.*— Firefox's Promise-based API -
chrome.*— Chrome's callback-based API (also supported in Firefox for compatibility)
// Chrome-style (callbacks):
chrome.tabs.query({active: true}, tabs => {
console.log(tabs[0].url);
});
// Firefox-style (Promises):
const tabs = await browser.tabs.query({active: true});
console.log(tabs[0].url);
// Works in both browsers via polyfill:
// https://github.com/mozilla/webextension-polyfill
Mozilla's webextension-polyfill package wraps the chrome.* callbacks to return Promises while maintaining compatibility with Chrome. Use it for cross-browser extensions.
Manifest V2 vs V3 in Firefox
Firefox supports both MV2 and MV3. The key difference from Chrome:
Firefox MV3 retains blocking webRequest:
// manifest.json (Firefox MV3)
{
"manifest_version": 3,
"permissions": ["declarativeNetRequest", "webRequest", "webRequestBlocking"],
...
}
On Chrome MV3, webRequestBlocking permission is unavailable. On Firefox MV3, it still works.
This means:
- uBlock Origin's advanced features work in Firefox MV3 but not Chrome MV3
- Extension-based ad blocking on Firefox is significantly more powerful
user.js in Extension Development
For development, you can set preferences via about:config that affect extension behavior:
# Allow insecure extension loading (unsigned, localhost)
xpinstall.signatures.required = false (Nightly only)
# For developer mode extensions
extensions.experiments.enabled = true
Most useful: use Firefox Developer Edition for extension development — it allows loading unsigned extensions without about:config changes.
Extension Storage API
Both Firefox and Chrome support:
browser.storage.local.set({ key: value });
browser.storage.sync.set({ key: value }); // Syncs via Firefox Sync
Firefox-specific: browser.storage.sync syncs via Firefox Sync (end-to-end encrypted). In Chrome, storage.sync syncs via Google. Both have the same API surface but different backend.
For extensions where privacy matters, Firefox's sync storage is preferable.
Content Script Isolation
Firefox uses a stronger content script isolation model than Chrome. Content scripts run in an isolated world — they share the DOM but have separate JavaScript heap.
This means:
- Extensions can't accidentally expose the page's
windowobject to malicious scripts - Variables set by the extension's content script are not visible to the page's scripts
The isolation is stricter in Firefox, which improves security but can occasionally cause compatibility issues with extensions that rely on shared page/extension contexts.
Building Cross-Browser Extensions
Best practices for extensions that run on both:
- Use
browser.*API with the webextension-polyfill - Include both MV2 and MV3 manifests (or use a build tool that generates both)
- Test on both Firefox Developer Edition and Chrome
- Use
browser.runtime.getBrowserInfo()for Firefox-specific branches
// Firefox-only branching
if (typeof browser !== 'undefined' && browser.runtime.getBrowserInfo) {
const browserInfo = await browser.runtime.getBrowserInfo();
if (browserInfo.name === 'Firefox') {
// Firefox-specific code
}
}
Extension Signing
Firefox requires extension signing for non-developer installations. Submit extensions to addons.mozilla.org (AMO) for review and signing.
For self-distribution (not via AMO): Mozilla offers a way to sign extensions yourself, but this is for enterprise deployment and requires a separate AMO account setup.
AMO's review process is thorough — it includes checking for obfuscated code (which is prohibited). This is the security model that makes Firefox extensions more trustworthy than Chrome's store.
Top comments (0)