This is the full developer documentation for ExtensionStart
# Introduction
> ExtensionStart documentation: from clone to a published, monetized browser extension.
ExtensionStart is a starter kit for paid browser extensions: a monorepo with sign-in, Stripe billing, paywalls, and store publishing wired up and tested. These docs walk the same path you will: from clone to a published, monetized extension on Chrome, Firefox, and Edge.
New here? Read the [Overview](/docs/getting-started/what-you-get/) to see what is in the box, then follow the five setup steps in order.
## Popular pages
[Section titled “Popular pages”](#popular-pages)
[Setup](/getting-started/quickstart/)Clone, run the wizard, load the extension in Chrome. About 10 minutes.
[Take payments](/getting-started/take-payments/)Stripe in test mode, ending with a checkout that flips your popup to premium.
[Paywalls and free limits](/guides/gates/)Pick a preset, list your premium features, stay inside store policy.
[Publish to the Chrome Web Store](/publishing/first-submission/)Developer account, listing, privacy tab, and what review expects.
## How the docs are organized
[Section titled “How the docs are organized”](#how-the-docs-are-organized)
* **Getting started**: the five-step path from clone to store listing. Follow it in order; each step ends with something you can verify.
* **Working with the codebase**: one guide per subsystem. Sign-in, payments, credits, paywalls, on-page UI, theming, announcements, the background service worker, and the module system.
* **Going to production**: submission runbooks and policy references for all three stores.
* **Reference**: CLI flags, environment variables, and the messaging protocol.
## Give the docs to your AI agent
[Section titled “Give the docs to your AI agent”](#give-the-docs-to-your-ai-agent)
* [extensionstart-docs.md](/docs/downloads/extensionstart-docs.md): every page in one Markdown file, sized for a context window.
* [llms.txt](/docs/llms.txt) · [llms-full.txt](/docs/llms-full.txt): machine-readable indexes.
* Append `.md` to any page URL for raw Markdown, or use the “Copy as Markdown” button next to each page title.
The [AI agents guide](/docs/guides/ai-agents/) covers the prompt recipes and Chrome DevTools MCP preset that ship in the repo.
## FAQ
[Section titled “FAQ”](#faq)
### Do I have to use Firebase?
[Section titled “Do I have to use Firebase?”](#do-i-have-to-use-firebase)
Firebase (Auth, Firestore, Cloud Functions) is the supported, wired-up path, and its free tier covers development. The billing core is port-based, so adapting it to another backend is a contained task rather than a rewrite.
### Which browsers are supported?
[Section titled “Which browsers are supported?”](#which-browsers-are-supported)
Chrome and Edge (MV3), plus Firefox. The build produces per-browser bundles, Chromium-only APIs are feature-guarded, and CI lints the Firefox build on every commit.
### How does billing work under the hood?
[Section titled “How does billing work under the hood?”](#how-does-billing-work-under-the-hood)
Stripe Checkout and the customer portal, driven by one Cloud Function. Webhooks write entitlements server-side; the extension reads a storage-backed snapshot. The server is the authority.
### Can I charge subscriptions, one-time payments, or credits?
[Section titled “Can I charge subscriptions, one-time payments, or credits?”](#can-i-charge-subscriptions-one-time-payments-or-credits)
All of them. Four monetization models ship ready to seed into Stripe; the wizard configures the one you pick. See [Payments](/docs/guides/billing/) and [Credits](/docs/guides/billing-credits/).
### What if I get stuck?
[Section titled “What if I get stuck?”](#what-if-i-get-stuck)
Every subsystem has its own guide in the sidebar. For anything else, email .
# Connect sign-in
> Step 2 of 5. Point the kit at your own Firebase project and sign in for real.
This guide connects the kit to your own Firebase project so Google and email sign-in work for real. Each step is one command or one console screen; the [Sign-in guide](/docs/guides/auth/) owns the detail.
## The checklist
[Section titled “The checklist”](#the-checklist)
1. **Install the Firebase CLI and log in:** `npm i -g firebase-tools`, then `firebase login`.
2. **Run the Firebase setup:**
```sh
pnpm create extstart --firebase
```
It creates or picks a project, writes the SDK config into every file that carries it, and prints deep links for the console steps below ([what it writes](/docs/guides/auth/#the-automated-path-recommended)).
3. **Enable the sign-in providers:** Google and Email/Password (and Anonymous, if you use anonymous-first) in the console screen the wizard linked.
4. **Create the Google OAuth client** and paste its ID as `WXT_GOOGLE_OAUTH_CLIENT_ID` when the wizard offers ([redirect-URI steps](/docs/guides/auth/#the-google-oauth-client-web-auth-flow-path)).
5. **Upgrade the project to the Blaze plan.** Step 3 deploys a Cloud Function, which the free Spark plan can’t do. The free-tier quota covers development.
6. **Rebuild and reload:** restart `pnpm dev` (env values are baked in at build time), then reload the extension on `chrome://extensions`.
## Verify
[Section titled “Verify”](#verify)
Open the popup and click **Sign in with Google**. The “Connect your Firebase project” notice is gone, the OAuth window completes, and your account shows in the Account tab.
If the sign-in window opens and closes without signing you in, the OAuth redirect URI doesn’t match your current extension ID; see [the fix](/docs/guides/auth/#the-google-oauth-client-web-auth-flow-path). To skip the OAuth client entirely, use the [offscreen popup path](/docs/guides/auth/#the-offscreen-popup-path) instead (Chromium only, zero OAuth config).
Next: [3. Take payments](/docs/getting-started/take-payments/).
# Build your first feature
> Step 4 of 5. Where your code goes, and the 2-file edit that makes a feature paid.
This guide shows you where your code goes and the 2-file edit that makes a feature paid. Build here any time; nothing before this step depends on it.
## Where your code goes
[Section titled “Where your code goes”](#where-your-code-goes)
The popup renders a card titled **“Your feature goes here”**. That is `apps/extension/components/YourFeature.tsx`, a small commented component that is yours to gut:
* Rename it freely (update the import in `apps/extension/entrypoints/popup/main.tsx`).
* Replace its two buttons with your real UI. Keep the primitives from `@extensionstart/ui` and the token color scales; both themes come free.
* Talk to the background only through `sendMessage` from `@/utils/messaging`. It is typed, so wrong payloads won’t compile.
Unlike the demos, `YourFeature.tsx` is core: the wizard never prunes it.
## Count free actions
[Section titled “Count free actions”](#count-free-actions)
The default `value-first` preset raises the paywall on the 10th recorded action. Record one after your feature does its work:
```ts
sendMessage("gateAction", { name: "your-free-action" }).catch(console.error);
```
This is the free button in `YourFeature.tsx`, verbatim.
## Make a feature premium: the 2-file edit
[Section titled “Make a feature premium: the 2-file edit”](#make-a-feature-premium-the-2-file-edit)
**File 1: `apps/extension/entrypoints/background/gates.ts`.** Add your feature ID to the list at the top:
```ts
export const PREMIUM_FEATURES = ["premium-demo", "export-pdf"];
```
**File 2: your call site.** Ask the gate engine before running the feature:
```ts
const decision = await sendMessage("gateFeature", { feature: "export-pdf" });
if (decision !== null) return; // the wall is already rendering; stop
// …run the premium feature…
```
The premium button in `YourFeature.tsx` does exactly this with the `"premium-demo"` ID, so you can compare against a working example.
## What the engine handles for you
[Section titled “What the engine handles for you”](#what-the-engine-handles-for-you)
* **The wall renders itself** in the popup, sidepanel, and content-script surfaces; you never build wall UI.
* **Dismissals cool down.** A dismissed paywall stays quiet for 24 hours. Every timing number lives in `gates.ts`.
* **Sign-in chains.** Signed-out users see the sign-in wall first, then the paywall. Sign-in never fires standalone, which keeps you inside [store policy](/docs/guides/gates/#stay-inside-chrome-web-store-policy).
* **Usage mirrors to the server.** Events flush to `POST /gate/events` every minute; anything with stakes reads server counters, not client ones.
## Delete the demos when ready
[Section titled “Delete the demos when ready”](#delete-the-demos-when-ready)
* **GateDemo** (`apps/extension/components/GateDemo.tsx`): once your own feature calls `gateFeature`, delete the file and its two `GateDemo` lines in `entrypoints/popup/main.tsx`. Don’t prune the `gate` module to remove it; that would delete your paywall engine too.
* **Highlighter**: the content-script demo is the `content-demo` module; rerun `pnpm create extstart` or see the [module system](/docs/guides/modules/).
Next: [5. Ship it](/docs/publishing/first-submission/). For presets, timing knobs, and manual wall control, see [Paywalls](/docs/guides/gates/).
# Repo tour
> The monorepo map: apps, packages, backend, and how they fit together.
ExtensionStart is a pnpm + Turborepo monorepo. Here’s the map, top to bottom.
## `apps/extension`: the extension app
[Section titled “apps/extension: the extension app”](#appsextension-the-extension-app)
WXT + React 19 + Tailwind 4 + Firebase. WXT **generates** `manifest.json` from `wxt.config.ts` per browser; never hand-edit a manifest.
* `entrypoints/background/`: one module per concern, imported by `index.ts` in a fixed order: `migrations` → `errors` → `logs` → `firebase` (auth) → `billing` → `gates` → `broadcasts` → `update-notice`. All event listeners are registered at the top level ([why that matters](/docs/guides/background/)).
* Surfaces: `popup` and `sidepanel` mount the same account/settings tabs (no separate options page); `welcome` is the first-run tour; `content` holds the shadow-DOM UI: status bar, gate walls, highlighter demo; `offscreen` is the auth fallback. Demo surfaces `newtab` and `devtools` build only with `WXT_DEMO_SURFACES=true`.
* `utils/`: the typed messaging protocol, settings store, shadow-UI mount, DOM observer, highlight persistence, error reporting, page bridge.
* `hooks/`: `useAuth`, `useBilling`, `useTheme`.
* `site.config.ts`: every user-facing name, email, URL, and pricing-copy string. Rebranding starts (and mostly ends) here.
## `packages/`: framework-free cores
[Section titled “packages/: framework-free cores”](#packages-framework-free-cores)
Each package is a framework-free core with a thin React subpath (`/react`) where UI bindings exist. Lint enforces importing only public entrypoints.
| package | what it is |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `core-ext` | `defineStore` / `defineStorageView` / `defineMessaging` / `defineProxyService` / `defineAlarm` / `defineMigrations` (the MV3 survival kit) plus a `/testing` chrome mock |
| `core-auth` | auth strategies (web-auth-flow default, offscreen fallback), anonymous-first linking, typed auth errors |
| `core-billing` | entitlement snapshot + states matrix, billing API client, storage-backed hooks (`useEntitlement('paid')`) |
| `gate` | trigger primitives and combinators, the gate evaluator (cooldowns/escalation/chaining), presets, and the `GateWall` UI (`/react`) |
| `ui` | Button/Card/Input/Badge/Skeleton/Dialog/Toast (CVA variants over Base UI) |
## `backend/functions`: one Hono app
[Section titled “backend/functions: one Hono app”](#backendfunctions-one-hono-app)
A single Hono app on Cloud Functions v2, deliberately self-contained: Firebase packs it standalone, so it can’t import workspace TypeScript. Routes: `/gateConfig`, `/billing/checkout`, `/billing/portal`, `/billing/webhook`, `/gate/events`, `/auth/revoke`, `/errors`. The billing core is port-based (StripeGateway / EntitlementStore / ClaimsWriter) so alternative payment providers can implement the same contract.
## `tooling/`
[Section titled “tooling/”](#tooling)
* `create/`: the `create-extstart` wizard ([reference](/docs/reference/cli/)).
* `config/`: shared ESLint/Prettier/tsconfig presets and the `module.schema.json` that validates module manifests.
## `module.json` everywhere
[Section titled “module.json everywhere”](#modulejson-everywhere)
Every prunable feature module carries a `module.json` manifest declaring its files, dependencies, env vars, and manifest permissions. The wizard’s pruner consumes these: dropping a module removes its code, deps, env entries, and permissions together. Details in the [module system guide](/docs/guides/modules/).
## Commands you’ll use
[Section titled “Commands you’ll use”](#commands-youll-use)
From the repo root:
```sh
pnpm build # build everything
pnpm typecheck # TypeScript, strict, workspace-wide
pnpm lint # ESLint, workspace-wide
pnpm turbo run test # unit tests
```
Extension-specific root aliases (the long form `pnpm --filter @extensionstart/extension