Setup wizard
pnpm create extstart configures your clone in place. It asks a few
questions, applies the answers to the repo, creates your Firebase or
Supabase project, and sets up payments. It downloads nothing and touches no file
outside the clone. Run it from anywhere inside the clone:
pnpm create extstartThe command resolves to the wizard published on npm, which hands over to the copy that ships in your clone, so the prompts always match the kit version you cloned. Menus take the arrow keys; Enter accepts the highlighted default.
Before you run it
Section titled “Before you run it”- Node 22.12 or newer. The clone’s
.nvmrcnames Node 22, sonvm installpicks up the latest 22 release. The wizard checks first and stops on an older Node. - A clean git tree. The wizard deletes and rewrites files, so it refuses
to run with uncommitted changes; every change is then one
git diffaway from review and onegit checkout .away from undo. - For Firebase, the CLI installed and logged in; for Supabase,
supabase logindone or a personal access token at hand (Setup, step 3), so the wizard can create the project. - Your payment provider’s test key, for the payments step.
The questions
Section titled “The questions”- Extension name and One-line description. Written to
apps/extension/site.config.ts, which the manifest and every surface read. Change them there any time. The same file holds your support email and the links behind “Help & support”, the privacy policy, and the welcome page; on Firebase the Firebase step points the links at your deployed website. The name also goes on your website (apps/site/src/site.ts, with the description as its hero line and meta description) and on the checkout return and uninstall pages; the rest of the website’s copy describes a sample product until you write yours, and the wizard’s Next list says so while it does. - Color palette: the brand colors of the extension and the website.
ember(the default: bright orange),blue,violet,teal,pink, ormono(black and white), plus any palette of your own inpackages/ui/palettes/. It points the extension and the website at the palette and writes its colors into the checkout return and uninstall pages and the store asset templates. Palettes shows what each one covers and how to make your own. - Backend:
firebase(the default) orsupabase. Both run the same API routes and the same security model. The wizard keeps the stack you pick and prunes the other one from your clone. It prints one line under your answer: on Firebase, the CLI version and your account, or what to install; on Supabase, whether an access token was found. - Side panel:
no(the default) ships the popup only.yesalso docks the popup’s tabs in Chrome’s side panel. The popup and the side panel. - Sign-in:
when-needed(the default) opens the popup as a guest and asks for an account in front of checkout and on features that need one.first-screenshows the sign-in form first. When sign-in is asked. - Billing model: what customers buy.
subscription(the default: monthly, yearly, lifetime),lifetime-only,hybrid-credits(a subscription with monthly credits, plus top-up packs),credits-only, orfree. A model writes the plans insite.config.tsand makes the matching paywall recipe the next default.freeremoves billing, the paywall and the on-page content script together, and skips the next two questions. Choose a billing model. - Payment provider:
stripe(the default),polar, orpaddle. Your clone keeps the provider you pick, and Stripe always: the wizard removes the other providers’ code, so you only read the one you use. Polar and Paddle are merchant of record and handle sales tax. What differs. - Paywall recipe: what stays free, and when the wall shows.
freemium(the default),free-trial,daily-limit,credits,paid-only, ormanual. It writesapps/extension/paywall.config.ts; the numbers (free uses, trial days, when to ask) live in that file and change without a rerun. Choose your paywall previews each one. - On-page UI:
no(the default on a fresh clone) oryes. The kit’s on-page demo is a content script on everyhttps://page that shows the status card and the page’s walls. It is the extension’s only access to websites, which the store shows at install as “Read and change all your data on all websites”.noremoves it, its card and its tests; asked only when billing stays. On-page UI.
On a clone the wizard has already configured, every question defaults
to what the clone says now (its palette, side panel, sign-in, plans,
provider, recipe, and on-page demo), so Enter changes nothing. A config you edited by
hand is offered as-is, which keeps it. The payment provider menu then
lists Stripe and the provider your clone kept; to move to one the wizard
removed, see Switch providers.
The wizard then applies your answers. The backend stack you did not
pick goes, and so do the payment providers you did not pick, billing
(on free), the side panel (on no), and the on-page demo (on no): their files are deleted,
their env vars and permissions
removed, and apps/extension/.env is created from the pruned template
(how pruning works). It also writes
apps/extension/extension-public-key.txt, which fixes your extension’s
ID so every clone and teammate gets the same one. Commit that file. Then
it regenerates the WXT types and typechecks the pruned tree
(--with-tests adds the unit suites).
Each of these steps, and each step below, is one line in the terminal: a
spinner with the seconds so far while it runs, then a check mark. A step
that fails prints what its command said. --verbose streams every
command instead.
The Firebase project
Section titled “The Firebase project”Create the Firebase project now? Enter runs the step. The question appears only when the Firebase CLI is installed and logged in; otherwise the Next list at the end says what to install.
- Firebase project:
create(the default) asks for a Project id (lowercase letters, digits, hyphens, 6 to 30 characters) and a Display name, then creates the project, about 30 seconds.existinglists your projects to pick from.
The wizard writes the project’s config into the extension and the
backend, creates the Firestore database, deploys the kit’s Firestore
rules, writes the example broadcast (broadcasts/welcome, the banner
the popup shows; what it is),
and switches on Email/Password sign-in. It deploys your website to the
project’s Hosting (https://<project-id>.web.app: the site template with
its help center and privacy policy) and points the extension’s “Help &
support”, privacy link, and welcome page at it, unless site.config.ts already has your own. Then
it prints what only the console can do. A project minutes old can refuse
the rules for a while; the payments step’s deploy ships them then.
The console checklist
Section titled “The console checklist”Three steps, each with a link into the right screen, listed only while they are still open. Do them in this order; the first one creates what the third one edits.
- Enable sign-in providers. Firebase console → Authentication → Sign-in method (click Get started if the page shows it instead of the provider list). Open Google, switch Enable on, pick a Project support email, Save. Firebase creates the OAuth consent screen and a web OAuth client at this point. If the checklist also lists Email/Password or Anonymous, enable them on the same page. Leave “Email link (passwordless)” off; the kit does not use it.
- Upgrade to the Blaze plan. The API runs on Cloud Functions, which the free Spark plan cannot deploy. Bottom of the console’s left sidebar → Spark plan → Upgrade → Blaze, then pick or create a Cloud Billing account; Google asks for a card here. Set a budget alert when offered, for example $5 a month; it emails you and cuts nothing off. The Blaze free tier is the same as Spark’s, so development costs nothing.
- Point the Google OAuth client at your extension. This is what
“Continue with Google” opens, and it needs your extension’s redirect
URI, which the wizard prints in full:
https://<extension-id>.chromiumapp.org/. Google Cloud console → APIs & Services → Credentials → Web client (auto created by Google Service) → Authorized redirect URIs → Add URI, paste it with the trailing slash, Save. No web client there? Create credentials → OAuth client ID, type Web application, the same redirect URI. While the consent screen is in Testing, only the test users listed there can sign in: add yourself now, and publish the app before the store release. The kit asks only for the basic profile scopes, so Google needs no verification review.
After the checklist the wizard asks: Paste your Google OAuth client id
now (Enter to skip). The Client ID ends in .apps.googleusercontent.com
and sits at the top of the web client’s page. Paste it and the wizard
writes it to apps/extension/.env as WXT_GOOGLE_OAUTH_CLIENT_ID. You
never need the client secret. Skipped it? Once Google sign-in is on, a
rerun reads the client ID from your project by itself:
pnpm create extstart --firebaseThe rerun is safe on a configured clone, needs no clean tree, and lists
only the console steps still open. It updates the Firebase lines of
.env and leaves the rest in place. Restart pnpm dev afterwards; env
values are baked in at build time.
The offscreen sign-in path that needs no OAuth client, the manual path without the wizard, and the fix when Google answers “Access blocked”: Authentication guide.
Ask your agent
Paste into Claude Code or Cursor from the repo root:
Enable Google and Email/Password sign-in for my Firebase project, add the redirect URI https://<extension-id>.chromiumapp.org/ to the web client Firebase created (or create a Web application OAuth client if there is none; pnpm create extstart --firebase writes apps/extension/extension-public-key.txt, which fixes the extension ID, and prints the exact redirect URI), and put its client ID in apps/extension/.env as WXT_GOOGLE_OAUTH_CLIENT_ID. Tell me which console screens you cannot reach so I do those myself.The Supabase project
Section titled “The Supabase project”Create the Supabase project now? Enter runs the step. It needs an
access token: the wizard finds the one supabase login stored, or
SUPABASE_ACCESS_TOKEN, and otherwise asks you to paste one from
your account’s tokens page.
The token stays in memory for the run.
- Supabase project:
create(the default) asks for the Organization (only when you have several), the Region, and a Project name, then creates the project on your organization’s plan and waits until it is up, a minute or two. The database password is generated and never printed: the kit never connects to Postgres directly, and the project’s database settings in the dashboard reset it when you need one.existinglists your projects to pick from.
The wizard writes the project URL, the publishable key, and the API URL
into apps/extension/.env, applies the kit’s migrations through the same
history supabase db push uses, deploys the Edge Function, and switches
on the auth settings the extension needs: email sign-in without a
confirmation mail, a password reset email with a code the extension
takes (unless your template already shows one), manual linking,
anonymous sign-ins when the extension runs anonymous-first, and the
extension’s redirect URL. The migration
also inserts the example broadcast the popup shows
(what it is). Then it prints
what is left.
The dashboard checklist
Section titled “The dashboard checklist”One step, and it is outside Supabase: Google sign-in needs an OAuth client that only Google Cloud can create.
- Create a Google OAuth client. Google Cloud console → APIs &
Services → Credentials → Create credentials → OAuth client
ID, type Web application. Authorized redirect URI:
https://<project-ref>.supabase.co/auth/v1/callback, which the wizard prints in full. While the consent screen is in Testing, only the test users listed there can sign in: add yourself now.
After the checklist the wizard asks: Paste your Google OAuth client id now (Enter to skip), then the Client secret. It switches Google on in your project with them; the secret goes to Supabase, never into a file. Skipped it? Rerun the step:
pnpm create extstart --supabaseThe rerun is safe on a configured clone, needs no clean tree, skips the
migrations already applied, redeploys the function, and lists only the
steps still open. Two more items appear only when an API call did not go
through: copying the publishable key into .env by hand, and adding the
redirect URL under Authentication → URL Configuration. The
manual path without the wizard: Supabase guide.
Payments
Section titled “Payments”Set up payments with stripe now? (or your provider). Asked as soon as
the Firebase or Supabase project is wired in and billing is kept. Enter runs the
step; n keeps it as one command for later:
pnpm create extstart --payments-setupRun on its own, it first asks for the provider among the ones your
clone ships, the one the clone is set to first (--payments answers
it; a clone that ships only Stripe skips the question). What it does on
Firebase, in order:
- Checks the Blaze plan. Not on it yet? It prints the upgrade link and waits: Upgraded to Blaze? Yes checks again. The console can take a moment to show the upgrade.
- Polar and Paddle only: Use the sandbox? Enter keeps the test
environment (
POLAR_SANDBOXorPADDLE_SANDBOXin the backend env). - Asks for the key, without echoing it, and confirms the paste with
its prefix and last four characters (
sk_test_…F456).- Stripe: the Secret key from the
API keys page,
sk_test_…. - Polar: an Organization Access Token from the sandbox dashboard,
under Settings,
polar_oat_…. - Paddle: an API key from Developer tools → Authentication,
pdl_…_apikey_….
- Stripe: the Secret key from the
API keys page,
- Stores the key and the settings: the key in Secret Manager (it
never lands in a file), and
backend/functions/.envwith the return URLs from your project id, the provider, the sandbox flag, and the card-trial length frompaywall.config.ts(BILLING_TRIAL_DAYS: checkout offers a trial only while it is above0). - Creates the products in your provider, by the kit’s lookup keys.
- Registers the webhook. Stripe: by API, secret stored. Polar and Paddle: the wizard prints the webhook URL for you to add in the dashboard, then asks for the endpoint secret.
- Deploys the API and the Firestore rules, a few minutes. Hosting (your website and the hosted pages) goes with it unless the Firebase step of the same run just deployed it.
- Runs the doctor, which checks the whole chain and names the fix for anything red.
A missing key, or a Blaze upgrade you keep for later, stops the step with the command to re-run; nothing is half done. The accounts to create first and the manual route, per provider: Stripe, Polar, Paddle.
On Supabase the same step runs against your project with the access
token from the project step: no Blaze check, and the only file it writes
is the provider line in apps/extension/.env. The key,
the provider, the sandbox flag, and the card-trial length from
paywall.config.ts are stored as function secrets, the products are
seeded, the webhook is registered (Stripe by API, Polar and Paddle by
paste), and the step checks that the API answers. Nothing is deployed:
the function reads its secrets on the next request, and it serves the
checkout return pages itself at /functions/v1/api/return/…, so no
website is needed. The manual route: Supabase guide.
The end of the run
Section titled “The end of the run”The wizard prints Next: pnpm dev
with the load-unpacked path, and one line for each setup step you
skipped, as the command that runs it and a link here. A step that
stopped short says what is missing first: a Firestore database that
could not be created, Supabase migrations that did not apply, or an Edge
Function that did not deploy. The payments step is offered only once the
project step has finished. Continue with
Setup, step 5.
What your answers remove
Section titled “What your answers remove”| Answer | Removes |
|---|---|
Backend firebase | the Supabase stack |
Backend supabase | the Firebase stack, and the uninstall survey, whose page and route live there |
Side panel no | the side panel page and the sidePanel permission |
Billing model free | checkout, plans, entitlements, the paywall, the on-page demo, and their permissions |
On-page UI no | the on-page demo: the content script, the status card and their e2e tests, so the extension asks for no access to websites. It carries no wiring markers, so a later run removes it too: pnpm create extstart --on-page no --yes |
| Payment provider | the providers you did not pick, adapter and seed (Stripe always stays) |
Everything else always ships: auth, the UI kit, your website
(apps/site), and broadcasts. The pruner leaves billing’s own
backend/** files in place when billing goes; the backend is one
self-contained function and unused routes are harmless. The Next
list then names BILLING_DISABLED=true, which lets the API deploy
without payment secrets and turns its billing routes off.
Headless mode
Section titled “Headless mode”pnpm create extstart --yes --side-panel no --name "My Ext"--yes accepts the defaults with no prompts. It keeps billing, the
side panel and the on-page demo unless --billing-model free,
--side-panel no or --on-page no says otherwise, and of the payment providers keeps Stripe and the one
--payments names (else the one the clone is set to).
--dry-run prints the prune plan, every file included, and changes
nothing. A headless run never creates cloud
resources without --firebase-create or --supabase-create, and the
payments step reads the key from the environment. Every flag:
CLI reference.
Rerunning
Section titled “Rerunning”The wizard is built for one configuration pass on a fresh clone. To
experiment, run it, inspect git diff, and git reset --hard to try a
different combination. A second pass starts from your clone’s answers
and rewrites only what you change. After the first run the module:*
wiring markers are stripped from the kept files, so a second pass can no
longer remove billing, the side panel, or the payment provider you
kept: it skips the side panel
question and leaves free off the billing menu. Run the first pass
with --keep-markers to keep that open. The Firebase, Supabase, and
payments steps rerun any time on their own: --firebase, --supabase,
and --payments-setup.
A recipe rewrites trial, prompt, and the two demo features
(your-free-action, premium-demo). Once you have renamed those to
your own features, a rerun sets trial and prompt only, and says so;
give your features the recipe’s values in paywall.config.ts, or run
/change-paywall-recipe in Claude Code.