Publishing an Extension to Firefox Add-ons (AMO)
Firefox Add-ons (addons.mozilla.org, AMO) is Mozilla's official extension store. Unlike Chrome Web Store, AMO provides detailed tools for developers and supports more flexible policies.
Creating a Developer Account
Register on addons.mozilla.org/developers via Mozilla account. No payment required — publishing is free.
Differences Between Firefox and Chrome
Firefox supports both manifests — MV2 and MV3, but with specifics:
{
"manifest_version": 2,
"browser_specific_settings": {
"gecko": {
"id": "[email protected]",
"strict_min_version": "91.0"
}
}
}
The browser_specific_settings.gecko.id field is required for Firefox. Without it, the extension won't receive updates correctly. ID format is email or GUID: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}.
Cross-Browser Compatibility
Most Chrome extensions work in Firefox with minimal changes. Main differences:
// Polyfill for both browsers
const browser = typeof globalThis.browser !== 'undefined'
? globalThis.browser
: globalThis.chrome;
// Or use webextension-polyfill from Mozilla
// npm install webextension-polyfill
import browser from 'webextension-polyfill';
webextension-polyfill wraps Chrome callbacks in promises and normalizes API differences.
Package Preparation
AMO requires source code for extensions using minification or bundling:
# Build extension
npm run build
# ZIP with final files
cd dist/
zip -r ../firefox-extension-1.0.0.zip .
# Separate ZIP with source code (required if using bundler)
cd ../
zip -r source-code-1.0.0.zip src/ package.json webpack.config.js \
--exclude "node_modules/*" \
--exclude ".git/*"
Without source code, review can be rejected with a request to provide it.
Upload via Web Interface
In Developer Hub → «Submit a New Add-on»:
- Distribution method: AMO (public) or Self-distribution (signed XPI without listing)
- Upload your add-on: upload ZIP
- Upload source code: if using bundler
- Describe your add-on: name, description, category
Self-Distribution and Signing
Any extension in Firefox must be signed by Mozilla, even if you don't plan to publish it in AMO. For corporate or personal use:
# Install web-ext
npm install -g web-ext
# Sign via AMO API (file will receive Mozilla signature)
web-ext sign \
--source-dir ./dist \
--artifacts-dir ./signed \
--api-key $AMO_API_KEY \
--api-secret $AMO_API_SECRET
Signed .xpi can be installed via about:addons or distributed independently.
AMO API for Automation
# Upload new version via API
curl -X POST \
"https://addons.mozilla.org/api/v5/addons/addon/{addon-id}/versions/" \
-H "Authorization: JWT $AMO_JWT_TOKEN" \
-F "[email protected]" \
-F "[email protected]"
JWT is generated from API credentials (api_key + api_secret):
import jwt from 'jsonwebtoken';
const token = jwt.sign(
{ iss: API_KEY, jti: Math.random().toString(), iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 60 },
API_SECRET
);
Full SDK: web-ext sign does this automatically.
Review Process on AMO
AMO has two levels of review:
Automated (Listed — Recommended not requested): most extensions pass automatic malware checks and get "Listed" status within a few hours.
Manual Review (Recommended badge): AMO team reviews code manually. Extension gets "Recommended" badge — more user trust, appearance in editorial picks. Review takes from a few days to several weeks.
Main rejection reasons: remotely loaded code, hidden functionality, excessive permissions, missing source code for obfuscated code.
Listing Localization
AMO supports full localization via web interface or _locales/ files in the extension:
_locales/
en_US/
messages.json
ru/
messages.json
de/
messages.json
// _locales/en_US/messages.json
{
"extensionName": { "message": "Extension Name" },
"extensionDescription": { "message": "Description for users" }
}
// manifest.json
{
"name": "__MSG_extensionName__",
"description": "__MSG_extensionDescription__"
}
Localized descriptions on AMO are added separately in Developer Hub — they are not taken automatically from _locales.







