Modules
The kit ships as the full repo. The setup wizard removes what your
answers rule out: the backend stack you did not pick, the payment
providers you did not pick (Stripe always stays), billing and the
paywall on a free extension, the side panel on a popup-only one. What
makes that safe is a contract: every module carries a module.json
manifest declaring everything it owns (code, npm dependencies, env
vars, manifest permissions). One declaration removes all of it together.
Manifests live at the package root for workspace packages
(packages/core-billing/module.json), under
apps/extension/modules/<id>/module.json for app-level modules, and
next to each payment provider’s seed
(backend/payments/polar/module.json), validated against
tooling/config/module.schema.json.
Anatomy of a manifest
Section titled “Anatomy of a manifest”{ "$schema": "../../tooling/config/module.schema.json", "id": "billing", "title": "Billing & paywall", "description": "Checkout and portal, webhook-written entitlements, and the paywall.", "files": ["packages/core-billing/**", "packages/paywall/**", "apps/extension/paywall.config.ts"], "dependsOn": ["auth"], "npmDependencies": { "@extensionstart/core-billing": "workspace:*" }, "env": [{ "name": "WXT_API_URL", "description": "deployed backend API base URL" }], "wiring": ["apps/extension/entrypoints/background/index.ts"]}filesare repo-root-relative: exact files, or a whole directory asdir/**. A module can own files outside its package.dependsOnis by module ID. The resolver keeps the dependencies of any kept module, even one an answer would drop, and says so. A module that depends on a backend stack (backend-firebase) is dropped when the other stack is chosen.- Modules the wizard never removes set
"removable": false: the core packages, the website, broadcasts. One that depends on a backend stack still goes with that stack (the uninstall survey on Supabase). - Env vars and permissions listed by several modules are pruned only when no kept module lists them.
permissionsis why the generated manifest shrinks when you prune: each module declares thechrome.*permissions it needs, and a smaller permission surface means a faster store review.
Wiring markers
Section titled “Wiring markers”A module’s code often touches shared files it doesn’t own: the background import order, the messaging protocol, surface roots. Those touchpoints carry marker comments so the pruner can strip them:
- Line marker: a
// module:<id>suffix ({/* module:<id> */}in JSX,# module:<id>in YAML) removes that line when<id>is dropped. - Block marker: everything from
module:<id>:startthroughmodule:<id>:end(inclusive) is removed. Blocks of different modules may nest.
Each manifest lists the shared files carrying its markers under wiring,
and every wiring file must carry at least one of them (a test checks
both directions). The pass criterion: any prune combination leaves
pnpm typecheck and pnpm lint green with zero dangling imports.
Markers work line by line, and Prettier decides where lines break. A marker after a statement stays on its line, but one after a JSX opening or closing tag does not: Prettier moves it to a line of its own. To drop a wrapper and keep what it wraps, put each tag in its own block:
{/* module:billing:start */}<PaidPlanNameProvider name={siteConfig.pricing.paidPlanName}> {/* module:billing:end */} <ToastProvider>{children}</ToastProvider> {/* module:billing:start */}</PaidPlanNameProvider>{/* module:billing:end */}The wizard formats every file it writes with the repo’s Prettier config once dependencies are installed, so a prune leaves no formatting diff.
The wizard strips all remaining markers at the end of its run (unless
--keep-markers). A later run keeps any module whose wiring lost its
markers, and says so: dropping it then would delete its files and leave
its imports behind.
The CI workflow (.github/workflows/ci.yml) is wiring too: its backend
steps sit in backend-firebase and backend-supabase blocks, so your
clone runs only the checks of the stack it ships. After pruning, the
wizard also refreshes pnpm-lock.yaml (CI installs with
--frozen-lockfile), so your first push builds green.
Backend files stay
Section titled “Backend files stay”A dropped feature module leaves its backend files in place: the Hono app
is one self-contained function and unused routes are harmless. Delete
them manually if you want a minimal backend. Two kinds of module are the
exception. The stack you do not choose goes whole (backend/functions
with backend-firebase, backend/supabase with backend-supabase),
feature routes included. A payment provider you do not choose
(payments-polar, payments-paddle) takes its adapter, its contract
test, its seed script, and its Supabase copy along, and its lines in the
provider switch carry its markers; the seed:* script that ran its seed
leaves backend/functions/package.json with it.
Add your own module
Section titled “Add your own module”A module of your own records what your feature owns. The wizard keeps it on every run; the manifest is there so each permission, env var, and test has an owner (CI checks that), and so your store listing’s permission justifications can say which feature needs what.
- Create the manifest (
apps/extension/modules/<id>/module.jsonfor an app-level feature) withid,title,description, andfilesglobs for everything the module owns. - Declare
npmDependencies,env(aWXT_name must exist inapps/extension/.env.example),permissions, anddependsOn. - Keep the manifest in sync: adding a file, dependency, env var, or
permission means updating
module.jsonin the same change.
Wiring markers are optional for your own module: nothing prunes it.