v1.0.0 to v2.0.0

12 added, 478 removed. Audit A to A.

---
name: firefox-extension
- description: Comprehensive guide for developing WebExtensions (browser extensions) for Mozilla Firefox, including Manifest V2/V3 configuration, all WebExtension APIs, security practices, web-ext CLI, and AMO submission.
+ description: Use when developing Firefox WebExtensions - Manifest V2/V3 configuration, WebExtension APIs, content security policy, web-ext CLI, cross-browser support with the polyfill, AMO submission, or debugging extensions
metadata:
author: mte90
- version: 1.0.0
+ version: 2.0.0
tags:
- firefox
- webextension
- browser-extension
- mozilla
- amo
- manifest-v3
---
# Firefox WebExtension Development
Complete reference for building, testing, and publishing browser extensions for Mozilla Firefox.
## Overview
Firefox extensions use the WebExtensions API with the `browser.*` namespace (Promise-based natively). Firefox supports both Manifest V2 and V3, with MV2 NOT deprecated (unlike Chrome).
**Key Characteristics:**
- Global namespace: `browser` (Promise-based)
- Both MV2 and MV3 supported
- Firefox-specific APIs: `sidebarAction`, `userScripts`, `contextualIdentities`, `protocol_handlers`
- Submission via AMO (addons.mozilla.org)
## Manifest Structure
### Manifest V3 (Recommended, Firefox 109+)
```json
{
"manifest_version": 3,
"name": "Extension Name",
"version": "1.0.0",
"description": "Brief description",
"browser_specific_settings": {
"gecko": {
"id": "extension@example.com",
"strict_min_version": "109.0"
},
"gecko_android": {
"strict_min_version": "109.0"
}
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon-16.png",
"48": "icons/icon-48.png",
"96": "icons/icon-96.png"
},
"default_title": "Extension Title"
},
"content_scripts": [
{
"matches": ["https://*/*"],
"js": ["content.js"],
"css": ["styles.css"],
"run_at": "document_idle"
}
],
"permissions": ["storage", "activeTab"],
"host_permissions": ["https://api.example.com/*"],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self';"
}
}
- ```
+ > See `references/security-crossbrowser.md` for full CSP reference.
+
### Manifest V2 (Still Supported)
```json
{
"manifest_version": 2,
"name": "Extension Name",
"version": "1.0.0",
"browser_specific_settings": {
"gecko": {
"id": "extension@example.com",
"strict_min_version": "78.0"
}
},
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_popup": "popup.html",
"default_icon": "icons/icon-48.png"
},
"permissions": ["storage", "activeTab", "https://*/*"]
}
```
### MV2 vs MV3 Key Differences
| Feature | MV2 | MV3 |
|---------|-----|-----|
| Toolbar button | `browser_action` | `action` |
| Background | `background.scripts` / `page` | `background.service_worker` |
| Host permissions | In `permissions` | Separate `host_permissions` |
| Default CSP | `script-src 'self'; object-src 'self';` | `script-src 'self'; upgrade-insecure-requests;` |
| Request blocking | `webRequest.onBeforeRequest` | `declarativeNetRequest` |
### All Manifest Keys Reference
**Metadata:**
- `name` (required) - Extension name
- `version` (required) - Version string (e.g., "1.0.0")
- `description` - Short description
- `author` - Author name
- `homepage_url` - Extension homepage
- `icons` - Extension icons object
**Firefox-Specific:**
- `browser_specific_settings.gecko.id` - **Required for AMO**
- `browser_specific_settings.gecko.strict_min_version` - Minimum Firefox version
- `browser_specific_settings.gecko.strict_max_version` - Maximum Firefox version
- `browser_specific_settings.gecko_android` - Android-specific settings
**Background & Scripts:**
- `background` - Service worker (MV3) or scripts/page (MV2)
- `content_scripts` - Scripts injected into pages
- `userScripts` - User script registration (Firefox-only)
- `declarative_net_request` - Rule-based request modification
**UI Components:**
- `action` (MV3) / `browser_action` (MV2) - Toolbar button
- `page_action` - Address bar button
- `sidebar_action` - Sidebar panel (Firefox-only)
- `options_ui` - Options page configuration
- `devtools_page` - DevTools extension page
**Permissions:**
- `permissions` - API permissions
- `host_permissions` (MV3) - Host access
- `optional_permissions` - Optional API permissions
- `optional_host_permissions` - Optional host access
**Other:**
- `commands` - Keyboard shortcuts
- `omnibox` - Address bar integration
- `web_accessible_resources` - Resources accessible from pages
- `protocol_handlers` - Custom protocol handlers
- `chrome_settings_overrides` - Override homepage/search
- `chrome_url_overrides` - Override new tab/bookmarks
- ## WebExtension APIs
-
- ### Complete API Namespace List (51 APIs)
-
- | API | Permission | Description |
- |-----|------------|-------------|
- | `action` | - | Toolbar button (MV3) |
- | `alarms` | `alarms` | Schedule code execution |
- | `bookmarks` | `bookmarks` | Bookmark management |
- | `browserAction` | - | Toolbar button (MV2) |
- | `browserSettings` | - | Browser settings |
- | `browsingData` | `browsingData` | Clear browsing data |
- | `clipboard` | `clipboardWrite` | Clipboard access |
- | `commands` | - | Keyboard shortcuts |
- | `contentScripts` | - | Register content scripts |
- | `contextualIdentities` | `contextualIdentities` | Container tabs (Firefox-only) |
- | `cookies` | `cookies` | Cookie management |
- | `declarativeNetRequest` | `declarativeNetRequest` | Rule-based request blocking |
- | `devtools` | `devtools` | DevTools integration |
- | `dns` | `dns` | DNS resolution |
- | `downloads` | `downloads` | Download management |
- | `events` | - | Common event types |
- | `extension` | - | Extension utilities |
- | `find` | `find` | Find text in pages |
- | `history` | `history` | Browser history |
- | `i18n` | - | Internationalization |
- | `identity` | `identity` | OAuth2 authentication |
- | `idle` | `idle` | Idle state detection |
- | `management` | `management` | Installed add-ons info |
- | `menus` | `menus` | Context menu items |
- | `notifications` | `notifications` | System notifications |
- | `omnibox` | - | Address bar suggestions |
- | `pageAction` | - | Address bar button (MV2) |
- | `permissions` | - | Runtime permissions |
- | `pkcs11` | `pkcs11` | PKCS#11 modules |
- | `privacy` | `privacy` | Privacy settings |
- | `proxy` | `proxy` | Request proxying |
- | `runtime` | - | Extension runtime |
- | `scripting` | `scripting` | Inject scripts/CSS (MV3) |
- | `search` | `search` | Search engines |
- | `sessions` | `sessions` | Closed tabs/windows |
- | `sidebarAction` | - | Sidebar (Firefox-only) |
- | `storage` | `storage` | Local/managed storage |
- | `tabGroups` | `tabGroups` | Tab groups |
- | `tabs` | `tabs` | Tab management |
- | `theme` | `theme` | Theme API |
- | `topSites` | `topSites` | Frequently visited |
- | `userScripts` | `userScripts` | User scripts (Firefox-only) |
- | `webNavigation` | `webNavigation` | Navigation events |
- | `webRequest` | `webRequest` | Request interception |
- | `windows` | - | Window management |
-
- ### Core API Usage Patterns
-
- **Tabs API:**
- ```javascript
- // Query tabs
- const tabs = await browser.tabs.query({ active: true, currentWindow: true });
-
- // Create tab
- const tab = await browser.tabs.create({ url: 'https://example.com' });
-
- // Update tab
- await browser.tabs.update(tabId, { active: true });
-
- // Send message to tab
- await browser.tabs.sendMessage(tabId, { action: 'update' });
-
- // Execute script (MV3)
- await browser.scripting.executeScript({
- target: { tabId },
- files: ['content.js']
- });
- ```
-
- **Storage API:**
- ```javascript
- // Save data
- await browser.storage.local.set({ key: 'value', settings: config });
-
- // Get data
- const { key, settings } = await browser.storage.local.get(['key', 'settings']);
-
- // Remove data
- await browser.storage.local.remove('key');
-
- // Clear all
- await browser.storage.local.clear();
-
- // Listen for changes
- browser.storage.onChanged.addListener((changes, area) => {
- if (changes.key) {
- console.log('Old:', changes.key.oldValue, 'New:', changes.key.newValue);
- }
- });
- ```
-
- **Runtime Messaging:**
- ```javascript
- // Content script → Background
- const response = await browser.runtime.sendMessage({ action: 'getData' });
-
- // Background listener
- browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
- if (message.action === 'getData') {
- return Promise.resolve({ data: 'response' });
- }
- });
-
- // Background → Content script
- browser.tabs.sendMessage(tabId, { action: 'update' });
-
- // External messaging (from web pages)
- browser.runtime.onMessageExternal.addListener((message, sender) => {
- if (sender.id === 'allowed-extension-id') {
- // Handle message
- }
- });
- ```
-
- **Context Menus:**
- ```javascript
- // Create context menu
- browser.menus.create({
- id: 'my-menu',
- title: 'My Menu Item',
- contexts: ['selection']
- });
-
- // Handle click
- browser.menus.onClicked.addListener((info, tab) => {
- if (info.menuItemId === 'my-menu') {
- console.log('Selected:', info.selectionText);
- }
- });
- ```
-
- **Alarms:**
- ```javascript
- // Create alarm
- browser.alarms.create('my-alarm', { delayInMinutes: 1, periodInMinutes: 5 });
-
- // Handle alarm
- browser.alarms.onAlarm.addListener((alarm) => {
- if (alarm.name === 'my-alarm') {
- // Do work
- }
- });
- ```
-
- **Notifications:**
- ```javascript
- // Show notification
- browser.notifications.create({
- type: 'basic',
- iconUrl: 'icon.png',
- title: 'Title',
- message: 'Message'
- });
- ```
-
- ### Firefox-Specific APIs
-
- **Sidebar Action:**
- ```javascript
- // Toggle sidebar
- browser.sidebarAction.open();
- browser.sidebarAction.close();
-
- // Set panel
- await browser.sidebarAction.setPanel({ panel: 'sidebar.html' });
- ```
-
- **Container Tabs (Contextual Identities):**
- ```javascript
- // List containers
- const containers = await browser.contextualIdentities.query({});
-
- // Create container
- const container = await browser.contextualIdentities.create({
- name: 'Work',
- color: 'blue',
- icon: 'briefcase'
- });
-
- // Create tab in container
- await browser.tabs.create({
- url: 'https://work.example.com',
- cookieStoreId: container.cookieStoreId
- });
- ```
-
- **User Scripts:**
- ```javascript
- // Register user script
- await browser.userScripts.register([{
- js: [{ file: 'script.js' }],
- matches: ['*://example.com/*'],
- runAt: 'document_start'
- }]);
- ```
-
- ## Security & Content Security Policy
-
- ### Default CSP
-
- **MV3:** `script-src 'self'; upgrade-insecure-requests;`
- **MV2:** `script-src 'self'; object-src 'self';`
-
- ### CSP Restrictions
-
- **Forbidden (causes AMO rejection):**
- - Remote script sources
- - `'unsafe-inline'`
- - `'unsafe-eval'` (except `'wasm-unsafe-eval'` for WebAssembly)
- - Data URLs for scripts
- - `eval()`, `new Function()`, string-based code execution
-
- **Allowed:**
- - `'self'` - Scripts from extension package
- - `'wasm-unsafe-eval'` - WebAssembly support
- - `http://localhost:<port>` - Development only (remove before submission)
-
- ### Custom CSP Example
-
- ```json
- {
- "content_security_policy": {
- "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';",
- "sandbox": "sandbox allow-scripts allow-forms allow-popups; script-src 'self';"
- }
- }
- ```
-
- ### Security Best Practices
-
- 1. **Package all dependencies locally** - No CDN scripts
- 2. **Request minimal permissions** - Use `activeTab` instead of `<all_urls>` when possible
- 3. **Use optional_permissions** - Request permissions at runtime for non-critical features
- 4. **Validate user input** - Sanitize HTML before `innerHTML`, validate URLs
- 5. **Use HTTPS** - All external requests should use HTTPS
- 6. **No obfuscated code** - Must be reviewable for AMO
-
## web-ext CLI Reference
### Installation
```bash
npm install -g web-ext
```
### Commands
**Run extension (development):**
```bash
web-ext run # Default Firefox
web-ext run --firefox-path /path/to/firefox # Specific Firefox
web-ext run --target firefox-android # Android
web-ext run --profile-create-new # Clean profile
web-ext run --start-url https://example.com # Start URL
web-ext run --verbose # Verbose output
web-ext run --no-reload # Disable auto-reload
```
**Lint extension:**
```bash
web-ext lint # Validate manifest and code
web-ext lint --warnings-as-errors
web-ext lint --self-hosted # Skip AMO-specific checks
```
**Build extension:**
```bash
web-ext build # Create .zip in web-ext-artifacts/
web-ext build --source-dir ./src
web-ext build --artifacts-dir ./dist
```
**Sign extension:**
```bash
web-ext sign --api-key $KEY --api-secret $SECRET
web-ext sign --channel listed # Public on AMO
web-ext sign --channel unlisted # Direct download
```
**Other commands:**
```bash
web-ext docs # Open documentation
web-ext dump-config # Show configuration
```
### Configuration File (.web-ext-config.js)
```javascript
module.exports = {
sourceDir: './src',
artifactsDir: './dist',
run: {
firefox: '/Applications/Firefox.app/Contents/MacOS/firefox',
startUrl: 'https://example.com',
pref: ['extensions.webextensions.debug=true']
},
build: {
overwriteDest: true
},
sign: {
apiKey: process.env.AMO_API_KEY,
apiSecret: process.env.AMO_API_SECRET,
channel: 'listed'
}
};
```
### Environment Variables
```bash
WEB_EXT_API_KEY=your_key
WEB_EXT_API_SECRET=your_secret
WEB_EXT_SOURCE_DIR=./src
WEB_EXT_ARTIFACTS_DIR=./dist
WEB_EXT_FIREFOX=/path/to/firefox
```
- ## AMO Submission Process
-
- ### Pre-Submission Checklist
-
- - [ ] Extension ID in `browser_specific_settings.gecko.id`
- - [ ] `web-ext lint` passes with no errors
- - [ ] All permissions are necessary and documented
- - [ ] Privacy policy included (if collecting data)
- - [ ] No obfuscated code
- - [ ] Source code available (if using build tools)
- - [ ] Icons: 48x48 and 96x96 minimum
- - [ ] Screenshots: minimum 464x200px
- - [ ] Description is clear and accurate
-
- ### Submission Steps
-
- 1. **Build extension:**
- ```bash
- web-ext build
- ```
-
- 2. **Create developer account:**
- - Visit https://addons.mozilla.org/developers/
- - Sign up and complete profile
-
- 3. **Submit:**
- - Go to Developer Hub → "Submit a New Add-on"
- - Upload `.zip` from `web-ext build`
- - Choose distribution: Listed (public) or Unlisted (direct)
-
- 4. **Fill listing:**
- - Name, description, categories
- - Screenshots, icons
- - Privacy policy URL (if collecting data)
- - Support email/URL
-
- 5. **Review process:**
- - Automated validation: 5-15 minutes
- - Human review: 1-7 days for listed extensions
- - Respond to reviewer comments promptly
-
- ### Common Rejection Reasons
-
- | Reason | Solution |
- |--------|----------|
- | Remote code execution | Package all scripts locally |
- | Unnecessary permissions | Remove unused permissions |
- | Obfuscated code | Provide source code |
- | Missing privacy policy | Add policy if collecting data |
- | Unclear functionality | Improve description |
- | CSP violations | Fix CSP to not allow remote scripts |
- | Hidden functionality | Disclose all features |
-
- ### Data Collection Requirements
-
- If extension collects/transmits user data, privacy policy must disclose:
- - What data is collected
- - How it's transmitted
- - Purpose of collection
- - User consent mechanism
- - Data retention policy
-
- ## Testing & Debugging
-
- ### Development Workflow
-
- ```bash
- # Start development
- web-ext run --verbose
-
- # Auto-reloads on file changes
- # Access via about:debugging
- ```
-
- ### Debugging Tools
-
- **Access debugging:**
- 1. Open `about:debugging#/runtime/this-firefox`
- 2. Click "Inspect" next to extension
-
- **Debug components:**
- - **Background:** Console in debugging page
- - **Popup:** Right-click popup → "Inspect"
- - **Content scripts:** Regular page DevTools Console
- - **Options page:** Right-click options → "Inspect"
-
- ### Debugging Commands
-
- ```javascript
- // Check manifest
- browser.runtime.getManifest();
-
- // Check permissions
- browser.permissions.contains({ permissions: ['tabs'] });
-
- // Get extension URL
- browser.runtime.getURL('/path/to/resource');
-
- // Check last error
- if (browser.runtime.lastError) {
- console.error(browser.runtime.lastError);
- }
-
- // Reload extension programmatically
- browser.runtime.reload();
- ```
-
- ### Testing Strategies
-
- **Unit Testing (Jest):**
- ```javascript
- import { mockBrowser } from 'webextension-pockito';
-
- describe('Extension', () => {
- beforeEach(() => mockBrowser.reset());
-
- test('storage', async () => {
- await browser.storage.local.set({ key: 'value' });
- const result = await browser.storage.local.get('key');
- expect(result.key).toBe('value');
- });
- });
- ```
-
- **Integration Testing (Selenium):**
- ```javascript
- const { Builder } = require('selenium-webdriver');
- const firefox = require('selenium-webdriver/firefox');
-
- const driver = await new Builder()
- .forBrowser('firefox')
- .setFirefoxOptions(new firefox.Options()
- .setPreference('extensions.autoDisableScopes', 0))
- .build();
- ```
-
## Best Practices
### Background Service Worker (MV3)
```javascript
// Keep service worker alive briefly
let keepAlive;
browser.runtime.onMessage.addListener((msg) => {
clearTimeout(keepAlive);
keepAlive = setTimeout(() => {}, 25000);
return handleMessage(msg);
});
```
### Error Handling
```javascript
async function safeAsync(fn) {
try {
return await fn();
} catch (error) {
console.error('Error:', error);
return { error: error.message };
}
}
```
- ### Cross-Browser Compatibility
-
- ```javascript
- import browser from 'webextension-polyfill';
-
- // Feature detection
- if (browser.sidebarAction) {
- // Firefox-specific
- browser.sidebarAction.open();
- }
-
- // Fallback pattern
- const action = browser.action || browser.browserAction;
- action.setPopup({ popup: 'popup.html' });
- ```
-
### Performance
- Use `browser.tabs.query()` with specific filters
- Debounce frequent operations
- Cache storage API results
- Use `alarms` API instead of `setInterval` in background
- ## Troubleshooting
-
- ### Common Issues
-
- | Error | Cause | Solution |
- |-------|-------|----------|
- | `browser is not defined` | Script not in extension context | Check manifest script paths |
- | Permission denied | Missing permission | Add to manifest permissions |
- | CSP violation | Remote script | Move script to extension package |
- | Service worker terminated | Long operation | Use alarms API, avoid blocking |
- | `webRequest` not blocking | MV3 limitation | Use `declarativeNetRequest` |
- | ID mismatch | Different IDs | Set consistent ID in manifest |
-
- ### Debug Commands
-
- ```bash
- # Validate manifest
- web-ext lint --verbose
-
- # Run with debug prefs
- web-ext run --pref extensions.webextensions.debug=true
-
- # Test in clean profile
- web-ext run --profile-create-new
-
- # Check specific Firefox
- web-ext run --firefox /path/to/firefox-beta
- ```
-
- ## Cross-Browser with webextension-polyfill
-
- ### Installation
-
- ```bash
- npm install webextension-polyfill
- ```
-
- ### Usage
-
- ```javascript
- // ES modules
- import browser from 'webextension-polyfill';
-
- // CommonJS
- const browser = require('webextension-polyfill');
-
- // Now browser.* works in Chrome with promises
- const tabs = await browser.tabs.query({ active: true });
- ```
-
- ### Manifest Setup
-
- ```json
- {
- "background": {
- "scripts": ["browser-polyfill.js", "background.js"]
- },
- "content_scripts": [{
- "matches": ["<all_urls>"],
- "js": ["browser-polyfill.js", "content.js"]
- }]
- }
- ```
-
- ### TypeScript Support
-
- ```bash
- npm install @types/webextension-polyfill
- ```
-
- ```typescript
- import browser from 'webextension-polyfill';
-
- async function getActiveTab(): Promise<browser.Tabs.Tab> {
- const [tab] = await browser.tabs.query({ active: true });
- return tab;
- }
- ```
-
## File Structure Template
```
my-extension/
├── manifest.json
├── background.js
├── content.js
├── popup.html
├── popup.js
├── options.html
├── options.js
├── browser-polyfill.js
├── styles/
│ ├── popup.css
│ └── content.css
├── icons/
│ ├── icon-16.png
│ ├── icon-32.png
│ ├── icon-48.png
│ └── icon-96.png
├── _locales/
│ ├── en/
│ │ └── messages.json
│ └── it/
│ └── messages.json
├── .web-ext-config.js
├── package.json
└── README.md
```
+ ## Deep Dives
+
+ Load these reference files on demand for detailed information:
+
+ - **WebExtension APIs** (51 namespaces, core patterns, Firefox-specific): `references/apis.md`
+ - **Security & Cross-Browser** (CSP, webextension-polyfill): `references/security-crossbrowser.md`
+ - **Testing & AMO** (debugging, troubleshooting, submission): `references/testing-amo.md`
+
## References
- [MDN WebExtensions](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions)
- [Extension Workshop](https://extensionworkshop.com/)
- [Manifest V3 Migration](https://extensionworkshop.com/documentation/develop/manifest-v3-migration-guide/)
- [web-ext CLI Reference](https://extensionworkshop.com/documentation/develop/web-ext-command-reference/)
- [AMO Developer Hub](https://addons.mozilla.org/developers/)
- [webextension-polyfill](https://github.com/mozilla/webextension-polyfill)
- [Browser Compatibility](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs)
-