# Firefox Add-ons

> AMO credentials, listed and unlisted channels, and source uploads.

Canonical: https://docs.extension.dev/publish/firefox
This page as Markdown: https://docs.extension.dev/publish/firefox.md
Section: Publish to stores
Siblings: [Publishing](https://docs.extension.dev/publish/overview.md) · [Chrome Web Store](https://docs.extension.dev/publish/chrome.md) · [Microsoft Edge Add-ons](https://docs.extension.dev/publish/edge.md) · [Executables](https://docs.extension.dev/publish/executables.md)
Map of every page: https://docs.extension.dev/llms.txt · Whole corpus: https://docs.extension.dev/llms-full.txt

---

# Firefox Add-ons

Firefox submissions go through the AMO API v5. Reach for this page when you
set up the AMO credentials, choose between a listed and an unlisted channel,
or need the source archive and data collection declaration a review asks for.

## Quick start

```bash
curl -s -X POST https://www.extension.dev/api/cli/stores/submit \
  -H "Authorization: Bearer $EXTENSION_DEV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"browsers":["firefox"],"buildSha":"<sha>","channel":"stable"}'
```

The platform creates an upload, polls its validation, then creates the
version from it. The same submission starts from the console's
**Submissions** tab or from an agent with `extension_submit`.


## How it works

### Credentials

AMO authenticates with a JWT signed from an issuer and a secret. The platform
mints the JWT per request with a 30 second expiry, so the secret never
travels and a captured token dies fast. Add the key and secret in the console
under **Project settings, Store publishing, Firefox**.

### Listed and unlisted

AMO has its own channel concept, separate from Extension.dev release
channels. The AMO channel is the **Automated release channel** setting in the
project's Firefox store settings, and it defaults to `unlisted`. A submission
with no channel saved goes unlisted, never listed.

`unlisted` is how to get a signed build for self distribution without a
public listing.

### The first submission

A first submission cannot be listed without an existing add-on GUID. It
either goes unlisted or creates a new add-on. When AMO assigns the GUID, the
platform writes it back to the project's Firefox settings, so the second
submission already knows its add-on.

### Source code uploads

AMO rejects a listed review of a bundled or minified build that carries no
sources, and every build the platform produces is bundled. Set **Source
archive** to **Attach the repository at the built commit** in the project's
Firefox store settings. Every Firefox submission then carries a zip of the
source repository at the commit that produced the build.

The archive is packaged during the submission and attached to the version
for Mozilla reviewers. The setting is off until you turn it on.

Reviewers also need the steps that turn that source into the submitted
build. Those come from `STORE.md`, which is submitted as the reviewer note.

The console's readiness checklist warns when a project has no archive
configured, on either channel: a listed review rejects a version without
sources, and an unlisted version is signed unread but Mozilla's policies
still apply to it, so a reviewer can ask for the sources or block the add-on
later. The dry run on the submit call does not read this setting. The
warning is advisory. AMO is what rejects.

Reviewers rebuild from the archive on their own machine, with Node 24 on
Ubuntu unless the note says otherwise, so the reviewer note in `STORE.md`
names the Node version the platform built with (22 unless the project set
one), the install command against the lockfile, the build command and the
output directory.

### Data collection consent

AMO requires every add-on to declare data collection permissions in the
manifest, under `browser_specific_settings.gecko.data_collection_permissions`.
An extension that collects no data still declares `{"required": ["none"]}`.
AMO rejects a new add-on without the key, and Mozilla has announced that
every existing extension must adopt it during 2026. The
[data collection permissions documentation](https://extensionworkshop.com/documentation/develop/firefox-builtin-data-consent/)
on Extension Workshop has the shape.

The preflight reads the key from the Firefox build under submission
(`manifest.json` inside its stored zip). When that build left no readable
artifact, it reads the source manifest at the build's commit with the
Extension.js `firefox:` and `gecko:` prefixes resolved the way the build
resolves them, and the check names the file it read either way.

The platform warns twice. A dry run on any project targeting Firefox returns
a `FIREFOX_DATA_COLLECTION_PERMISSIONS` warning, and the submission logs a
warning when the uploaded package's `manifest.json` is missing the key. Both
are advisory. AMO is what rejects.

The one refusal is a package whose `manifest.json` cannot be read out of the
zip at all. That submission stops with `FIREFOX_DATA_COLLECTION_UNVERIFIED`
before anything is uploaded.

### Reviewer notes, release notes and category

Firefox reviewer notes and release notes are read from `STORE.md` in the
project root and submitted automatically. See [Publishing](/publish/overview).

The `- Category:` line under `## Listing` is shared by every store, and AMO
has fewer categories than the Chrome Web Store: it has no Accessibility and
no Productivity. When the shared name is one AMO lacks, put a `- Category:`
line under `## Firefox Add-ons` with one of AMO's own names; `Other` is the
closest when none fits. The per-store line wins for Firefox and leaves the
other stores on the shared one.

An unlisted upload needs no category. AMO signs the file for self
distribution and the listing is never sent, so the preflight only warns. A
listed submission is refused by AMO after the file is sent when the add-on
has no AMO category, so there the preflight blocks until `STORE.md` names
one.

### Watching

The **Submissions** tab follows the submission to a terminal state, and
`GET /api/projects/{id}/stores/status` returns the same. The states are the
eight on [Publishing](/publish/overview#watching). Firefox tracks by the
version id the submission returns.

## Reference

### Secrets

| Value | Secret | Notes |
| --- | --- | --- |
| API key (issuer) | `STORE_FIREFOX_API_KEY` | From the AMO API credentials page |
| API secret | `STORE_FIREFOX_API_SECRET` | Shown once, at creation |

### Store settings

| Setting | Stored as | Values |
| --- | --- | --- |
| Add-on GUID | `storeId` | Empty for a first unlisted submission, written back by the platform |
| Automated release channel | `releaseChannel` | `unlisted` (default) or `listed` |
| Source archive | `sourceArchive` | `none` (default) or `repository` |

### AMO channels

| AMO channel | Result |
| --- | --- |
| `listed` | Published on addons.mozilla.org, goes through review |
| `unlisted` | Signed and returned to you, not listed publicly |

### Warnings and refusals

| Code | Kind | Meaning |
| --- | --- | --- |
| `FIREFOX_DATA_COLLECTION_PERMISSIONS` | Warning when the Firefox manifest of the build lacks it | Declare `data_collection_permissions` in the manifest; the warning names the file it read |
| `FIREFOX_DATA_COLLECTION_UNVERIFIED` | Refusal before upload | `manifest.json` could not be read out of the zip |

## When it fails

- **Validation fails.** Validation failures surface while the upload is
  polled, before a version exists. Read the validation messages. The version
  was never created.
- **A first submission asks for `listed`.** It is refused by design. Submit
  unlisted or let it create the add-on, then go listed once the GUID exists.
- **A store is blocked.** The preflight, `dryRun` on the submit call, reports
  credential health and the console page that fixes a blocked store.
- **The submission stops with `FIREFOX_DATA_COLLECTION_UNVERIFIED`.** The
  package's `manifest.json` could not be read. Rebuild the package and submit
  again.

## Next

- [Publishing](/publish/overview) for `STORE.md` and dry runs
- [HTTP API](/automate/api) for the submit and status endpoints
