Publishing
Publishing submits a released build to a store. The platform holds the store credentials and runs the submission; you trigger it through any of three doors, and all three are the same submission underneath.
All three start from a project the platform already builds, so the project is on extension.dev before any of them applies. If yours is still a repository, or a folder you scaffolded locally and never pushed, Import is the way in.
Three ways in
Console. The project's Stores tab submits a released build and follows each store's status. Store configuration lives in project settings.
Agent. With the MCP server configured, extension_submit sends a released build into a store's review queue, dry run by default, and extension_release_promote promotes a build to a channel first.
API. POST /api/cli/stores/submit and POST /api/cli/release/promote, authenticated with a project token. See the HTTP API.
Supported stores
| Store | Browser | Page |
|---|---|---|
| Chrome Web Store | Chrome | Chrome |
| Firefox Add-ons | Firefox | Firefox |
| Microsoft Edge Add-ons | Edge | Edge |
All three run on Linux on both plans. Safari and the App Store are a fourth lane and a paid one, builds and submissions together, and the submit job runs on a macOS runner, which is paid work on our machines. On the free plan a Safari build and an App Store submission are both refused 403 with macos_runner_not_included before anything is dispatched, and no allowance is spent. A paid workspace carries 15 App Store submissions a month. See extension.dev/pricing.
A build can also ship outside every store as a standalone launcher. See Executables.
Credentials
Credentials are project store settings, entered once and reused by every submission. The platform holds them as secrets, never in a database, and never returns them. The secret names are exact:
| Store | Secrets |
|---|---|
| Chrome | STORE_CHROME_CLIENT_ID, STORE_CHROME_CLIENT_SECRET, STORE_CHROME_REFRESH_TOKEN, or STORE_CHROME_SERVICE_ACCOUNT_JSON alone |
| Firefox | STORE_FIREFOX_API_KEY, STORE_FIREFOX_API_SECRET |
| Edge | STORE_EDGE_CLIENT_ID, STORE_EDGE_CLIENT_SECRET |
| Safari | Nine secrets covering the App Store Connect key and signing assets |
Add them in the console under Project settings, Stores, one panel per store. The /stores/new path that a preflight verdict returns is a redirect to that same page.
Credential health
Health is a stored verdict per store, and you run the check from the store's panel in Project settings, Stores.
With the secrets typed into the form, the panel's test button calls POST /api/projects/{id}/stores/test/{store} and answers at once. The fields are write only, so a saved store comes back empty; the same button then queues the check on the project mirror instead, and the verdict lands when that run finishes. The queued check counts as one dispatch.
The stored verdict feeds the dry run below. It does not stop a real submission: POST /api/cli/stores/submit without dryRun dispatches for every store you list, failing credentials included. The console is the one door that blocks, because its submission page keeps the submit button disabled while a blocking check fails.
Dry runs
dryRun: true on POST /api/cli/stores/submit runs the preflight, and an agent's extension_submit defaults to it. The response returns a verdict per store and dispatches nothing:
| Field | Meaning |
|---|---|
configured | The store has credentials |
healthy | The credentials pass the health probe |
ok | The store would accept this submission |
reason | Why a blocked store is blocked |
consoleUrl | The console page that fixes that store |
This verdict has two doors, and both are outside the console: the API endpoint with a project token, and the agent tool.
The console runs a preflight of its own, in a different shape. Open Stores, pick a store, start a submission, and choosing a release runs a readiness checklist over the release, the store settings, and the listing. It reports blocking checks and disables the submit button, and it never returns the five fields above.
Safe and broad
Every submission mode is classified safe or broad. Safe modes upload a draft and submit nothing for review. Broad modes reach store review or the public. Draft-only safe mode is a per-store setting, and an explicit setting is always honored, whoever saved it.
Agent submissions default safe. extension_submit defaults to a dry run, and a submission with dryRun: false through the MCP, the CLI, or an API token uploads without publishing when the store settings never stated a submission mode: Edge stops at its draft and Chrome skips the publish call. An explicit publishAfter: true (Edge) or skipPublish: false (Chrome) still goes to review. Console submissions follow the store setting shown on the panel. A project whose store workflow predates the safe default is refused with SAFE_MODE_INPUT_UNSUPPORTED rather than sent to review; save the project settings to resync the workflow, then submit again.
A safe run is recorded as draft, not as submitted. The two words mean different things and the record keeps them apart: a draft is a package sitting in the store, a submission is a review round that started.
Submission notes
A STORE.md in the project root is parsed automatically. Firefox reviewer notes and release notes, and Edge certification notes, ride along with the submission.
Watching
The Stores tab follows a submission to a terminal state, and GET /api/projects/{id}/stores/status returns the same. Eight states exist, and every store uses these same eight words:
| State | Meaning |
|---|---|
draft | The package is uploaded and no review round was asked for |
dry_run | The preflight ran and nothing reached the store |
pending | Queued, the store has not answered yet |
submitted | A review round started |
approved | The store accepted this version |
rejected | The store refused this version |
failed | The run itself failed before the store answered |
unknown | The run ended and no state could be read from it |
unknown is the default for anything unreadable, including a step that died before it wrote its result. It is never read as submitted. A state the platform did not read from the run is not a state it reports.
When it fails
- Run the dry run first, from the API or the agent. The
reasonsays why a store is blocked and theconsoleUrlnames the console page that fixes it. From the console, open the store's submission page and read the checklist. - A store failing its health check means its credentials, not your build. Rotate the secret named in the verdict from Project settings, Stores.
- A store with no published health yet returns an unknown verdict, not a failure. Run the check from its panel before you trust the dry run.
- A submission reads
unknown. Nothing readable came back from the run, so nothing is claimed. Open the run, read why it stopped, and submit again. - A submission reads
draftwhen you expected a review round. The run was a safe mode, so the package uploaded and nothing was submitted. Turn off draft-only for that store, or ask for review on the run itself. - The API and agent doors need a project token. Without one, submit from the console.
- Submissions read from a release channel. A build that was never promoted has nothing to submit. See Channels.
Next
- Chrome, Firefox and Edge for each store's flow
- HTTP API for the submit, promote, and status endpoints
- Executables to ship without a store