Comprehensive guide to writing browser extensions that work across Chrome, Firefox, Safari, and Edge with proper feature detection and polyfills.
Overview
Browser extensions share a common WebExtensions API standard, but implementations differ significantly. This skill covers how to handle those differences.
This skill covers:
API compatibility matrices
Polyfill usage and patterns
Feature detection techniques
Browser-specific workarounds
Manifest differences
This skill does NOT cover:
General JavaScript compatibility (use caniuse.com)
Extension store submission (see extension-anti-patterns skill)
UI framework differences
Quick Reference
Browser API Namespaces
Browser
Namespace
Promises
Polyfill Needed
Chrome
chrome.*
Callbacks
Yes
Firefox
browser.*
Native
No
Safari
browser.*
Native
No
Edge
chrome.*
Callbacks
Yes
Universal Pattern
// Use webextension-polyfill for consistent APIimport browser from'webextension-polyfill';
// Now works in all browsers with Promisesconst tabs = await browser.tabs.query({ active: true });
API Compatibility Matrix
Core APIs
API
Chrome
Firefox
Safari
Edge
Notes
action.*
✓
✓
✓
✓
MV3 only
alarms.*
✓
✓
✓
✓
Standard
bookmarks.*
✓
✓
✗
✓
Safari: no support
browserAction.*
MV2
✓
MV2
MV2
Use action in MV3
commands.*
✓
✓
◐
✓
Safari: limited
contextMenus.*
✓
✓
✓
✓
Standard
cookies.*
✓
✓
◐
✓
Safari: restrictions
downloads.*
✓
✓
✗
✓
Safari: no support
history.*
✓
✓
✗
✓
Safari: no support
i18n.*
✓
✓
✓
✓
Standard
identity.*
✓
◐
✗
✓
Firefox: partial
idle.*
✓
✓
✗
✓
Safari: no support
management.*
✓
✓
✗
✓
Safari: no support
notifications.*
✓
✓
✗
✓
Safari: no support
permissions.*
✓
✓
◐
✓
Safari: limited
runtime.*
✓
✓
✓
✓
Standard
scripting.*
✓
✓
◐
✓
Safari: limited
storage.*
✓
✓
✓
✓
Standard
tabs.*
✓
✓
◐
✓
Safari: some limits
webNavigation.*
✓
✓
◐
✓
Safari: limited
webRequest.*
✓
✓
◐
✓
Safari: observe only
windows.*
✓
✓
◐
✓
Safari: limited
Advanced APIs
API
Chrome
Firefox
Safari
Edge
Workaround
declarativeNetRequest
✓
◐
◐
✓
Use webRequest
offscreen
109+
✗
✗
109+
Content script
sidePanel
114+
✗
✗
114+
Use popup
storage.session
102+
115+
16.4+
102+
Use local + clear
userScripts
120+
✓
✗
120+
Content scripts
Polyfill Setup
Using webextension-polyfill
The Mozilla webextension-polyfill normalizes the Chrome callback-style API to Firefox's Promise-based API.
// Chrome service workers terminate after ~5 minutes// Always persist state to storage// BAD: State lost on worker terminationlet count = 0;
// GOOD: Persist to storageconst countStorage = storage.defineItem<number>('local:count', {
defaultValue: 0
});
asyncfunctionincrement() {
const count = await countStorage.getValue();
await countStorage.setValue(count + 1);
}
Offscreen Documents (Chrome/Edge only)
// For DOM access in MV3 service workerif (hasAPI('offscreen')) {
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['DOM_PARSER'],
justification: 'Parse HTML content'
});
}