Build your feature
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”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. Your first feature takes its place; every later
feature is a new component next to it, modeled on the first (the
add-feature recipe in .claude/commands/ walks both).
- Rename it to your feature’s name in three places: the file, its
import in
apps/extension/components/HubPage.tsx, and everymodule.jsonthat lists it (grep -rl YourFeature --include=module.json .). Until the manifests match,pnpm testfails and names the entry to change. - Replace its two buttons with your real UI. Keep the primitives from
@extensionstart/uiand the color roles (bg-background,text-foreground, and the rest); both themes come free. - Talk to the background only through
sendMessagefrom@/utils/messaging. It is typed, so wrong payloads won’t compile.
Unlike the demos, YourFeature.tsx is core: the wizard never prunes it.
Give the core action a free allowance
Section titled “Give the core action a free allowance”The default freemium recipe lists the core action as
{ free: 10, per: "ever" }: ten free uses for good. The wall follows
the tenth use and every click after it, with Pro removing the limit. Ask before the work,
exactly like a Pro feature:
const { allowed } = await sendMessage("access", { feature: "your-free-action" });if (!allowed) return; // the allowance is spent; the wall is upThis is the free button’s call in YourFeature.tsx, and
<UsageMeter feature="your-free-action" /> inside it
shows “7/10” with a progress line along the button’s edge (the tooltip
and screen readers get “7 of 10 free uses left”). A feature listed as
"free" runs every time; the same call just counts it, so fire and
forget it after the work.
Make a feature paid: the 2-file edit
Section titled “Make a feature paid: the 2-file edit”File 1. List the feature and what it costs:
features: { "your-free-action": { free: 10, per: "ever" }, "export-pdf": "pro", // Pro only // or a free allowance: summarize: { free: 5, per: "day" } // or credits: "ai-image": { credits: 5 }},File 2: your call site. Ask the engine before running the feature:
const { allowed } = await sendMessage("access", { feature: "export-pdf" });if (!allowed) return; // the wall is already rendering; stop// …run the feature…The premium button in YourFeature.tsx does exactly this with the
"premium-demo" id, so you can compare against a working example. Put
<LockBadge feature="export-pdf" /> inside the control (a lock and the
paid plan’s name, pricing.paidPlanName in site.config.ts), so users
see it is paid before they click. For a limited
feature, <UsageMeter feature="summarize" /> inside the control shows
“3/5 today” with a progress line along its edge. Give a control that
holds the meter relative overflow-hidden.
What the engine handles for you
Section titled “What the engine handles for you”- The wall renders itself in the popup, the side panel, and the on-page demo, with copy for the reason (Pro feature, limit reached, out of credits, sign in); you never build wall UI.
- One call, one verdict.
accesschecks, counts free uses, and consumes credits before the work. Feature ids are typed from the config. - 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.
- Usage mirrors to the server. Accesses queue locally and flush to
POST /gate/eventsonce 25 are queued or the oldest is an hour old; anything with stakes, such as credits or a card trial, is enforced on the server, not by client counters.
Delete the demos when ready
Section titled “Delete the demos when ready”- YourFeature is the seed, not a demo: rename it and replace its two
buttons with your real UI. The
accesscalls it carries are the entire paywall integration. - The on-page status card (
components/content/StatusCard.tsx, while your clone has it) calls the same two demo ids from everyhttps://page. Put your feature’s on-page work behind itsaccesscall, or remove the whole on-page demo if your product has no on-page UI: commit, then runpnpm create extstart --on-page no --yes. It deletes the content script, the card and their e2e spec, and the extension then asks for no access to websites (see On-page UI). - The demo ids (
your-free-action,premium-demo) go frompaywall.config.tsonce nothing calls them. The e2e specs click the demo’s buttons throughFREE_ACTIONandPREMIUM_BUTTONinapps/extension/e2e/fixtures.ts: point them at your own core and paid controls, and rewrite or delete the specs that only show the demo. A wall in a spec ismodals(page)from the same file, so a snackbar your feature shows never counts as one.
For recipes, trials, limits, and manual wall control, see the paywall guide.