# Quickstart

> Create an extension, build it for every browser, and share the result.

Canonical: https://docs.extension.dev/guides/quickstart
This page as Markdown: https://docs.extension.dev/guides/quickstart.md
Section: Start here
Siblings: [Overview](https://docs.extension.dev/index.md) · [Introduction](https://docs.extension.dev/guides/introduction.md)
Map of every page: https://docs.extension.dev/llms.txt · Whole corpus: https://docs.extension.dev/llms-full.txt

---

# Quickstart

The Quickstart takes a new extension from a template to a build you can hand
to someone else. Follow it the first time you connect a project. Every later
project repeats steps 3 to 6.

## Quick start

```bash
npx extension@latest create my-extension --template=init
cd my-extension
npm install
npm run dev
```

The first command scaffolds the extension and commits it. The last one opens a
browser with the extension loaded and reloads it on every file change.


## How it works

### 1. Create an extension

The scaffold above is the Extension.js framework. The other way in is to
[import a repository](/guides/import) you already have.

[extension.dev/new](https://www.extension.dev/new) shows six starting
surfaces. Each tile opens `extension.dev/import?template=<slug>` directly, and
**Browse templates** opens
[templates.extension.dev](https://templates.extension.dev), a separate app
where the full catalog runs live in the page. Its **Import to extension.dev**
button sends you to the same `extension.dev/import?template=<slug>` address,
which is where step 3 continues. The catalog covers popups, sidebars, content
scripts, and framework combinations. For a full-page surface instead of a
popup, start from the
[New Tab template](https://templates.extension.dev/newtab). See
[Templates](/templates/).

### 2. Run it locally

```bash
npm run dev
```

The dev server launches a browser with the extension loaded and reloads it on
every file change. It binds to `127.0.0.1:8080`, and `--port` moves it.

On a machine with no browser, such as a container or a CI runner, add
`--no-browser`. The dev server still starts, builds, and watches. It opens
nothing.

```bash
npm run dev -- --no-browser --port 9000
```

This step is the Extension.js framework, documented at
[extension.js.org](https://extension.js.org). Nothing has touched the platform
yet.

### 3. Connect the project

Step 1 left a repository with one commit, on the branch `git init` gives a new
repository. What is missing is the remote, because the platform builds from a
repository GitHub can serve.

```bash
gh repo create my-extension --private --source=. --push
```

Any way of creating the remote works, and `gh` is the shortest. There is no
`git add` and no `git commit` step here. Both answer "nothing to commit,
working tree clean" on a fresh scaffold. A repository that reaches the import
with no commits at all is the one case with nothing to build, and the import
says so instead of starting a build. See
[Import](/guides/import#a-folder-on-your-machine-with-no-remote-yet).

Open [extension.dev/new](https://www.extension.dev/new) and sign in with
GitHub. Signed in, that address forwards you to the console's `/new`, the
repository picker. Choosing a repository there sends you to
`extension.dev/import` with it selected. The import analyzes the tree, finds
the manifest, detects the browsers the extension declares, and infers a build
command and output directory per browser.

`/import` needs a GitHub App installation before it can write anything, and it
asks for one in the middle of the flow. The selector lists the installations
you already have and offers **Authorize new installation**. That sends you to
`github.com/apps/extensiondev/installations/new`, asks which account and
repositories the App may touch, and returns you to the same import page with
the installation selected. The button that does the work reads **Create
repository and deploy** for a template and **Create project** for a
repository.

Sign-in is gated during the alpha. An account that is not on the invite list
lands on `/subscribe` with an explanation instead of reaching the console.

See [Import](/guides/import) for what the analysis reads,
[Projects](/guides/projects) for what a project owns, and
[Workspaces](/guides/workspaces) for who can see it.

### 4. Build for every browser

Push a commit. The project builds one artifact per configured browser from
that commit, in a build repository the platform owns, and lists the build
under **Builds** in the console. Build minutes never burn on your account.

A build that starts from a push runs without you, so it is metered on its own.
Free carries 25 of them a month. Past that the push is refused, and
**Rebuild** on the latest build under **Builds** runs the same commit by hand.
See [extension.dev/pricing](https://www.extension.dev/pricing).

Each build records the commit it came from, the browsers it produced, and the
integrity hash of every artifact, so a build can be checked later without
trusting the console that served it.

### 5. Share the build

A build does not need a release to be looked at. Share it from the build in
the console, or from an agent with the [MCP server](/tools/mcp) configured
by calling `extension_preview_web` with `share: true`. Either way the link
renders the exact bytes of that build in a browser emulator.

The platform hands out two share links, and they run on two clocks. The
uploaded preview is a `?preview=` address that lives for your plan's window
and can be revoked. The other comes from `extension_publish` in the agent,
which uploads nothing and answers with your project's own build page,
carrying a `?share=` token when the project is private. The table under Reference has both. See
[Share an unpublished build for review](/share/unpublished-build-for-review)
and [Publish a build](/tools/publish).

### 6. Promote and publish

A new project answers to two release channels, `preview` and `stable`. Any
other channel is one you create. Promotion checks the build's environment
stamp against the target channel's binding. See
[Environments](/guides/environments) and
[Promote gates](/builds/promote-gates).

When a build sits in `stable`, submit it to the stores from the project's
**Submissions** tab in the console, from an agent with `extension_submit`, or
with `POST /api/cli/stores/submit` and a [project token](/tools/access-tokens).
Store credentials are entered once in project settings and reused by every
submission. See [Channels](/builds/channels) and
[Publishing](/publish/overview).

## Reference

| Share link | Minted by | Address | Lifetime |
| --- | --- | --- | --- |
| Uploaded preview | Share in the console, or `extension_preview_web` with `share: true` | `?preview=` | 30 days on Free, 365 days on Pro; revocable |
| Build link | `extension_publish` from an agent | `?share=` on a private project | 7 days by default. Ask for more with `ttlHours`, within the plan's share window: 30 days on Free, 365 days on Pro |
| Channel link | A public channel, or any channel of a public project | `/channels/<channel>`, no token | Never expires |

| Allowance | Free | Pro |
| --- | --- | --- |
| Builds started by a push, per month | 25 | 350 |
| Projects per workspace | 1 | 10 |

## When it fails

- **`npm run dev` errors before the platform is involved.** That is the
  framework. Debug it with the docs at
  [extension.js.org](https://extension.js.org).
- **The project exists but shows no builds.** Builds start on push. Push a
  commit, then watch **Builds** in the console.
- **Pushes built earlier this month and stopped.** Free carries 25 builds from
  a push a month, and you have used them. Click **Rebuild** on the latest
  build under **Builds** to run the same commit now. The count resets at the
  start of the next month.
- **A share link stopped opening because it expired.** An uploaded preview
  link lasts 30 days on Free and 365 days on Pro. Share the same build again
  and the expired address is reused with a fresh window, so the link you
  already sent starts working again. A `?share=` token from
  `extension_publish` lasts 7 days unless the agent passed `ttlHours`, and
  you get a new one by asking the agent to publish again.
- **A share link stopped opening because it was revoked.** A revoked address
  is dead for good. Re-sharing the same build mints a different address. Send
  the new link; the old one never comes back.
- **Promotion to `stable` is refused.** The build's environment stamp does not
  match the channel's binding. Build for the target channel, then promote. See
  [Promote gates](/builds/promote-gates).

## Next

- [Access tokens](/tools/access-tokens) to run these steps from CI
- [MCP server](/tools/mcp) to run them from a coding agent
- [Import](/guides/import) for what the analysis reads
