# Store releases

> A store submission is the release of that build for that browser. The row per version per store, what each store act does to it, and what the Releases page and the MCP answer.

Canonical: https://docs.extension.dev/builds/store-releases
This page as Markdown: https://docs.extension.dev/builds/store-releases.md
Section: Console: build and release
Siblings: [Console overview](https://docs.extension.dev/console/overview.md) · [Usage and limits](https://docs.extension.dev/console/usage.md) · [Builds](https://docs.extension.dev/builds/overview.md) · [Channels](https://docs.extension.dev/builds/channels.md) · [Releases](https://docs.extension.dev/builds/releases.md) · [Promote gates](https://docs.extension.dev/builds/promote-gates.md) · [Integrity](https://docs.extension.dev/builds/integrity.md)
Map of every page: https://docs.extension.dev/llms.txt · Whole corpus: https://docs.extension.dev/llms-full.txt

---

# Store releases

A store submission is the release of that build for that browser. Submitting
a build creates the release row for that store and version, and every store
act after it (publish, the store's review, a withdrawal, a newer version)
advances the same row. Reach for this page when you need to know what is
live in each store, which record says so, and what `extension_submit` writes.

Channels stay the way testers get a build: promoting a build pins a channel
to it, and that is a separate record. Nothing has to be promoted before a
store submission, and a store submission never moves a channel.

## Quick start

```
GET /api/projects/{id}/releases
```

The answer carries `storeReleases`, one row per store and version, beside
the channel rows it always carried. `summary.activeReleases` counts the rows
at `live`; `summary.liveChannels` counts the channels every browser's
promote run proved live.

From an agent, `extension_release_status {include: ["history"]}` answers
`value.storeReleases` with the same rows, and `{include: ["stores"]}` carries
each store's newest row as `release`.

## How it works

### The row

One row per store and version. Several submissions of one version (a Chrome
draft uploaded twice, two TestFlight builds of one App Store version) advance
one row; the newest record wins.

| Field | Meaning |
| --- | --- |
| `id` | `<store>:<version>` |
| `store` | `chrome`, `firefox`, `edge` or `safari` |
| `browser` | The browser the store serves, the same word |
| `version` | The version the store names: `storeVersion.packaged` when the platform stamped one, else the submission's version |
| `buildSha` | The seven character sha of the build the row stands on |
| `state` | `uploaded`, `in-review`, `live`, `rejected`, `withdrawn` or `superseded` |
| `storeWord` | The store's own word for that version as last read (`PUBLISHED`, `InReview`, `WAITING_FOR_REVIEW`), empty when the platform holds none |
| `readAt` | When that word was read |
| `source` | Where it was read: `publish-answer` (the store's answer to a publish, cancel or review send), `poll` (the platform's store poller), `run` (the store's answer to the upload, read by the store run), or `inferred` (no store word; the state was inferred from the recorded submission) |
| `visibility` | `public` or `unlisted`, only when the store said so (Chrome's publish answer or poll); absent otherwise. A live row with no visibility is live for an audience the platform has not read |
| `storeVersion` | `{packaged, displayed, source, buildNumber?}` when the platform stamped the package |
| `submissionId`, `submittedAt`, `updatedAt` | The submission that advanced the row, and when |
| `storeUrl`, `failureReason` | The listing, and the store's refusal when it refused the last act |
| `channel` | The channel the build was promoted to, when one pins it |

A row whose `source` is `inferred` says what the platform recorded, not what
the store said. It is shown as such and never grounds a next step: an agent
reads the store again before acting on it.

Visibility is read, never inferred. A Chrome item Chrome calls `PUBLISHED`
with visibility `unlisted`, or a Firefox version AMO signed for self
distribution, is live for its audience only, and a row never reads it as
public. Where no store said who can find the version, `visibility` is
absent and the row says nothing about it.

### What a submit does to the row

A submit uploads the build. The row appears at `uploaded` once the
platform's record says the store took the package. A run that failed before
any store step, and a submission the platform dispatched and has no answer
for yet, create no row: the submissions page shows them, the Releases page
does not.

### What each store's acts do to the row

| Store | Act | Row |
| --- | --- | --- |
| Chrome | Upload a draft | `uploaded` |
| Chrome | Publish (send to review) | `in-review` with Chrome's word |
| Chrome | Chrome approves and publishes | `live` (`PUBLISHED`) |
| Chrome | Chrome rejects | `rejected` |
| Chrome | Cancel review | `withdrawn`; the draft stays on the dashboard |
| Edge | Upload a draft | `uploaded` |
| Edge | Publish (certification) | `in-review` (`InReview`); Edge locks the product until certification ends |
| Edge | Certification passes and the product serves the version | `live` |
| Edge | Partner Center refuses the publish | stays `uploaded` with `failureReason` in Microsoft's words |
| Firefox | Unlisted upload, signed | `live` for self distribution once AMO signed it |
| Firefox | Listed upload | `in-review` (awaiting review) |
| Firefox | AMO approves | `live`; AMO rejects: `rejected` |
| Firefox | A newer version arrives before review | the older row `superseded`, AMO's own word |
| Safari | TestFlight upload | `uploaded` with the build number |
| Safari | Send to App Review | `in-review` (`WAITING_FOR_REVIEW`, `IN_REVIEW`) |
| Safari | Apple approves | `live`; Apple rejects: `rejected` |
| Safari | Remove from review | `withdrawn` (`DEVELOPER_REJECTED`); the build stays on TestFlight |

Across stores, the last rule is the same: when a newer version of one store
reaches `live`, the older `live` row of that store reads `superseded`. That
transition is inferred, since no store says it in one word, and the row says
so in `source`.

### How the state is read from the record

The platform's submission record carries `status`, the store's answers
(`storeUpload`, `storeReview`, `publish`, `cancel`) and the version it
stamped. The row is read from them in this order:

| Record | State |
| --- | --- |
| `draft`, `uploaded` | `uploaded` |
| `submitted` | `in-review` |
| `approved`, `published` | `live` |
| `rejected` | `rejected` |
| `superseded` | `superseded` |
| a `cancel` the store accepted, on a record that is no longer in review | `withdrawn` |
| `failed` while the store holds the package (`storeUpload` or `storeSubmissionId`) | `uploaded` with `failureReason` |
| `failed` before the store, `pending`, `dry_run`, `unknown` | no row |

`storeWord`, `readAt` and `source` come from the newest of `storeReview`
and `storeUpload`; with neither, `source` is `inferred`.

### What counts as active

`summary.activeReleases` is the number of rows at `live`. One store has at
most one live row, the newest version it serves. `summary.liveChannels` is
what `activeReleases` counted before: channels whose every browser's promote
run concluded success. The two are different facts and are shown apart.

### What the Releases page shows

The console Releases page lists the store rows in a **Stores** section:
store, version, build, state, the store's word with when and where it was
read, and a link to the submission. The overview cards read **Live in
stores** from `activeReleases` and **Live channels** from `liveChannels`.
While every reading is zero and the history is empty, the cards step aside.

### What the MCP answers

`extension_release_status {include: ["history"]}` answers `value.storeReleases`
and `value.summary` (`activeReleases`, `liveChannels`, `channels`), and its
hint leads with what is live where. `{include: ["stores"]}` carries each
store's newest row as `release` on that store, so "is it live on Chrome"
reads the same answer on both.

`extension_submit` sends no channel. Its answer's `value.channel` is the
channel the platform found the build promoted to, when one pins it; a submit
of a build no channel pins answers no channel.

### The submission's channel

A store submission used to carry `channel: "stable"`, a label that named no
channel and changed nothing uploaded. A submission's `channel` is now the
channel whose pin for that browser is the build submitted, read from the
project's channel pins, else absent. `POST /api/cli/stores/submit` and
`POST /api/projects/{id}/stores/{store}/submissions` accept `channel` in the
body and ignore it, and the store run receives `channel` only when such a
pin exists. The approval of a submit binds `browsers` and `buildSha`.

## Reference

### Store release row

| Field | Type |
| --- | --- |
| `id` | `<store>:<version>` |
| `store`, `browser` | `chrome`, `firefox`, `edge`, `safari` |
| `version`, `buildSha` | string |
| `state` | `uploaded`, `in-review`, `live`, `rejected`, `withdrawn`, `superseded` |
| `storeWord`, `readAt` | string, empty when the platform holds no store word |
| `source` | `publish-answer`, `poll`, `run`, `inferred` |
| `visibility` | `public` or `unlisted`, optional, only from the store's own word |
| `storeVersion` | `{packaged, displayed, source, buildNumber?}`, optional |
| `submissionId`, `submittedAt`, `updatedAt` | string |
| `storeUrl`, `failureReason`, `channel` | string, optional |

### Summary

| Field | Counts |
| --- | --- |
| `activeReleases` | Store rows at `live` |
| `liveChannels` | Channels every browser's promote run proved live |
| `channels` | Channels that exist, shipped or not |

## When it fails

- **A store is live and the Releases page reads no live store.** The
  platform's record for that submission has not read the store's word yet.
  Ask for a store read with `extension_release_status {include: ["stores"],
  refresh: ["<store>"]}` or the Refresh control on the submission, then read
  again.
- **The row reads `inferred`.** The platform recorded the submission and
  holds no store word for it. Read the store before acting on the row.
- **A submission the record says failed shows no row.** The store never
  received the package. The submissions page carries the failed step and
  its last log lines.
- **`channel` is absent on a new submission.** No channel pins the build
  that was submitted. Promote the build to a channel first when the records
  should match, or leave it: the store release stands on its own.
- **A live row carries no `visibility`.** The store has not said who can
  find the version. Read it in the store's dashboard before calling it
  public.

## Next

- [Releases](/builds/releases)
- [Channels](/builds/channels)
- [Publishing](/publish/overview)
