# Builds

> What a build is, what starts one, what it produces, and how to read it.

Canonical: https://docs.extension.dev/builds/overview
This page as Markdown: https://docs.extension.dev/builds/overview.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) · [Channels](https://docs.extension.dev/builds/channels.md) · [Releases](https://docs.extension.dev/builds/releases.md) · [Store releases](https://docs.extension.dev/builds/store-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

---

# Builds

A build is one run of the deploy workflow over one commit, producing one
packed extension per enabled browser. Reach for this page when a build
appeared after a push or a **Rebuild** and you want to know what ran, what
came out and where to read it.

## Quick start

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

The list answers one item per build, each with a status per browser. Open
one with `GET /api/projects/{id}/builds/{buildId}` to read its artifacts,
logs and run URL.


## How it works

### Where a build runs

A build is one dispatch of the deploy workflow in the project's mirror
repository. The mirror lives in a platform-owned organization: your source is
checked out into the runner there, and every Actions minute burns there,
never in your repository.

### What a build produces

Each enabled browser carries its own `installCommand`, `buildCommand` and
`outputDirectory`, and each emits its own artifacts. A browser builds only
when the project enables it in **Project settings, Browser builds** and the
dispatch names it. A dispatch that leaves a browser out produces no artifact
for that browser, and nothing downstream can invent one.

Safari carries two conditions the other three do not. It builds only on a
paid plan, and it emits the web extension payload alone. Builds run on Linux,
and the Mac app that wraps the payload is assembled at submit time.
Executables are narrower still: builds emit launchers for `chrome`, `edge`
and `firefox` only. See [Executables](/publish/executables).

### What starts a build

Five triggers exist: `console`, `onboarding`, `webhook`, `release` and `api`.
Only `webhook` is a push build, and it covers two events: a git push, and a
pull request opened, reopened or updated.

When **Project settings, Deployment** names a production branch, only pushes
to that branch build. Pushes to other branches are skipped and never counted.
An empty field builds every branch. The filter reads `push` events alone, so
a pull request builds whatever that field says. **Build pull requests**, in
the same section, is the control for that half. It is on unless you turn it
off.

Push and PR webhooks are debounced 75 seconds. A webhook resolves to one of
three decisions, `dispatch`, `debounce` or `drop-duplicate`, while the other
four triggers always dispatch. Concurrency is keyed per ref with
cancel-in-progress, so a new push cancels the older build on the same ref
instead of billing both.

### What a build spends

Free carries 25 push builds a month and refuses the twenty sixth. Pro carries
350. Both webhook events draw on that one allowance. The other four triggers
are never counted against it, but every dispatch, whatever started it, counts
against the plan's build ceiling: 100 a month on free, 350 on pro. See
[extension.dev/pricing](https://www.extension.dev/pricing) and
[Usage and limits](/console/usage).

### Environments and rebuilds

Every build is stamped with the environment it compiled in: `staging` by
default, `production` when the trigger asks for it. Promotion checks the
stamp against the target channel. See [Promote gates](/builds/promote-gates).

Rebuilding a commit whose artifacts a live channel serves, for an environment
other than the one that channel requires, is refused with
`409 REBUILD_REPLACES_LIVE_ARTIFACT`. Pass `force: true` to overwrite
anyway. A production rebuild of a commit that only `preview` serves is an
upgrade and passes without force.

## Reference

### Artifacts per browser

| Artifact | Contents |
| --- | --- |
| `<browser>-extension.zip` | The packed extension |
| `<browser>.json` | Build metadata |
| `<browser>.integrity.json` | The integrity record, see [Integrity](/builds/integrity) |

### Browsers

| Browser | Builds | Executable |
| --- | --- | --- |
| `chrome` | yes | yes |
| `edge` | yes | yes |
| `firefox` | yes | yes |
| `safari` | paid plan only, payload alone | no |

### Routes

| Route | Returns |
| --- | --- |
| `GET /api/projects/{id}/builds` | The build list |
| `GET /api/projects/{id}/builds/{buildId}` | One build, with `runUrl` and a row per browser |
| `GET /api/projects/{id}/builds/{buildId}/artifacts` | Its artifacts |
| `GET /api/projects/{id}/builds/{buildId}/logs` | Logs, streamed from the CI jobs and grouped by browser |
| `GET /api/projects/{id}/builds/{buildId}/integrity/{browser}` | The integrity record for one browser |
| `GET /api/projects/{id}/builds/{buildId}/usage` | The project's `USAGE.md`, as JSON with a `markdown` field |
| `GET /api/projects/{id}/builds/live-status?runId=<runId>` | One workflow run's `status` and `conclusion` |

`live-status` takes the run id, not the build id. Read the run id from
`runUrl` on the build, or from the same field on its logs. Without `runId`
the route answers `400`.

### Limits

| Limit | Free | Pro |
| --- | --- | --- |
| Build dispatches a month, every trigger | 100 | 350 |
| Push builds a month, `webhook` only | 25 | 350 |
| Webhook debounce | 75 seconds | 75 seconds |

## When it fails

- **Pushed, nothing built.** A push within 75 seconds of the previous one is
  decided `debounce` or `drop-duplicate`, not `dispatch`. Wait it out, or
  dispatch from the console or the API.
- **Build canceled.** A newer push on the same ref canceled it. The newer
  build has the ref, and only one is billed.
- **A push stopped building part way through the month.** That is the push
  build ceiling, 25 a month on free. Only the `webhook` trigger counts
  against it, so the console, the API and a release still dispatch. Click
  **Build this commit now** in the console to run the same commit, free.
- **A store submission finds no artifact.** The submission reads the artifact
  the build produced for that browser. A build whose dispatch left the
  browser out has none. Enable the browser in **Project settings, Browser
  builds** and build the commit again.
- **A browser built but has no executable.** Only `chrome`, `edge` and
  `firefox` produce launchers.
- **`409 REBUILD_REPLACES_LIVE_ARTIFACT`.** A live channel serves the
  artifacts you are about to replace with a build for another environment.
  Rebuild for the environment the channel requires, build a new commit, or
  pass `force: true` if replacing them is the point.

## Next

- [Channels](/builds/channels)
- [Releases](/builds/releases)
- [Integrity](/builds/integrity)
