# Publish a build

> Ask your AI agent to publish a build and get its page on extension.dev, with a download and a run command for each browser.

Canonical: https://docs.extension.dev/tools/publish
This page as Markdown: https://docs.extension.dev/tools/publish.md
Section: Agent tools and the CLI
Siblings: [MCP server](https://docs.extension.dev/tools/mcp.md) · [Server flags and telemetry](https://docs.extension.dev/tools/mcp/flags.md) · [Platform tools](https://docs.extension.dev/tools/mcp/platform.md) · [Run on Safari](https://docs.extension.dev/tools/mcp/safari.md) · [Agent skill](https://docs.extension.dev/tools/skill.md) · [Pair with Chrome DevTools MCP](https://docs.extension.dev/tools/chrome-devtools-mcp.md) · [Access tokens](https://docs.extension.dev/tools/access-tokens.md) · [CLI](https://docs.extension.dev/tools/cli.md) · [Agent bridge](https://docs.extension.dev/tools/agent-bridge.md) · [Agent logs](https://docs.extension.dev/tools/logs.md) · [Inspect and act](https://docs.extension.dev/tools/inspect-and-act.md)
Map of every page: https://docs.extension.dev/llms.txt · Whole corpus: https://docs.extension.dev/llms-full.txt

---

# Publish a build

Ask your AI agent to publish, and it returns your build's page on
extension.dev, with a download and a run command for each browser. Reach for
it when a tester, a reviewer or a teammate needs a build the platform already
made.

## Quick start

Start the [MCP server](/tools/mcp) with `--features=local,platform`, log in,
and ask the agent:

```
Publish my latest build and give me the link
```

The agent calls [`extension_publish`](/tools/mcp/publish). The answer carries
`link`, the link to send, `tellThem`, one sentence to send with it, and
`previewCommands`, one run command per browser the build targets.


## How it works

`extension_publish` uploads nothing. It links a build the platform recorded,
so the project needs one completed build first. Without `buildSha` the link
is the newest successful build's page,
`https://<workspace>.extension.dev/<project>/builds/<sha>`. With `buildSha` it
is the build you name. When the build list cannot be read, the link falls
back to the project page.

The build page has a download for each browser with the install steps beside
it, and the answer adds a run command per browser. To hand out the bytes in
your own `dist/` right now, upload them instead:
[Share an unpublished build for review](/share/unpublished-build-for-review).
Sending an extension to a store is [Publishing](/publish/overview).

### Build links and channel links

There are two links. A build link opens one build's page and always shows
that build. A channel link,
`https://<workspace>.extension.dev/<project>/channels/<channel>`, always
opens the newest build on the channel and never expires. On a public
project, or a public channel, send the channel link. Otherwise send the build
link and say when it stops working. `tellThem` says it for you. See
[Build links and channel links](/share/build-and-channel-links).

A public project's build link has no token and no expiry. A private
project's build link adds a `?share=` token. The token lives 7 days unless
the agent passes `ttlHours`, up to the plan's share window, 30 days on Free
and 365 days on Pro. The page opens for whoever holds the link while it
lasts. Visibility is a setting of the extension.dev project, not of its
GitHub repository.

### Which project answers

The token decides the project, not the folder you stand in. Log in with the
`extension_auth` tool and `action: "login"`, or run
`npx @extension.dev/mcp login --project <workspace>/<project>`, then approve
the printed code at `extension.dev/device` in a browser signed in to
extension.dev. The token lands in `~/.config/extension-dev/auth.json`, under
`$XDG_CONFIG_HOME` if you set one, or `%APPDATA%\extension-dev\auth.json` on
Windows. See [Access tokens](/tools/access-tokens).

The tool reads `EXTENSION_DEV_TOKEN`, then the stored login. With several
logins stored, its `project` argument or `EXTENSION_DEV_PROJECT` picks one,
and that login wins over `EXTENSION_DEV_TOKEN`.

### From a script

The tool posts to `POST /api/cli/publish` with the project token in the
`Authorization` header. A script can make the same call. The body carries
only `ttlHours` and `buildSha`, so no path, manifest or repository name leaves
your machine. See the [HTTP API](/automate/api).

## Reference

| `extension_publish` input | What it does | Default |
| --- | --- | --- |
| `buildSha` | Link that build's page. A sha with no completed build is refused | The newest successful build |
| `ttlHours` | Token lifetime on a private project, up to the plan's share window. Ignored on a public project | 168 (7 days) |
| `project` | Which stored login to use, as `<workspace>/<project>` | `EXTENSION_DEV_TOKEN`, then the most recent login |
| `api` | Platform base URL | `https://www.extension.dev` |

| Answer field | Meaning |
| --- | --- |
| `link` | The link to send: `kind` (`build` or `channel`), `url`, `expiresAt`, `pointsAt` and `audience` |
| `tellThem` | One sentence to send with the link |
| `shareUrl` | The build page address |
| `visibility` | Whether the project is public or private |
| `buildSha`, `version`, `builtAt` | The build the link serves |
| `previewCommands` | One run command per browser the build targets |
| `expiresAt` | When a private project's build link stops working |

| Code | Status | Meaning |
| --- | --- | --- |
| `BAD_TOKEN` | 401 | The token is wrong or expired |
| `TOKEN_IN_QUERY` | 401 | The token was sent in the query string instead of the header |
| `TOKEN_REVOKED` | 401 | The token was revoked |
| `PROJECT_NOT_FOUND` | 404 | The token's project does not exist here |
| `UNKNOWN_BUILD` | 404 | The `buildSha` is not a build of this project |

## When it fails

- **The agent has no `extension_publish` tool**: the server started without platform tools. Start it with `--features=local,platform`. See [Platform tools](/tools/mcp/platform).
- **An auth error**: the token is missing, wrong or expired. Device login tokens live at most seven days. Log in again with `extension_auth`.
- **The URL names a project you did not expect**: the platform answered for the token's project. Read the slug in the URL before you send it, and pass `project` for the one you mean.
- **`UNKNOWN_BUILD`**: the `buildSha` has no completed build. Leave it out to link the newest successful build.
- **`PROJECT_NOT_FOUND`**: the platform does not know the project. [Import the repository](/guides/import) first, or [push a local folder to GitHub](/guides/import#a-folder-on-your-machine-with-no-remote-yet) and then import it.
- **You expected a store listing**: this tool links a build page. Store submission starts at [Publishing](/publish/overview).
- **You expected a link that outlives the token**: send a channel link. A workspace owner or admin makes a channel public, or makes the project public. See [Build links and channel links](/share/build-and-channel-links).

## Next

- [Share an unpublished build for review](/share/unpublished-build-for-review) uploads a local `dist/` instead
- [Build links and channel links](/share/build-and-channel-links) for which link to send
- [Public project pages](/share/public-pages) for the pages a public project gets
- [Access tokens](/tools/access-tokens) covers minting and scoping tokens
