Every runtime message in the extension is declared once, in
apps/extension/utils/messaging.ts, as the ExtensionProtocol interface:
key = message type, parameter = payload, return type = response. Both ends
are typed end to end; never use raw runtime.sendMessage.
the background does the API call and opens the provider’s checkout tab, so the flow survives the popup closing
billingPortal
none
none
opens the customer portal the same way
billingPrices
none
none
refreshes storage.local.prices from the public GET /billing/prices when the cache is older than an hour; the plan picker sends it on open and renders the cache meanwhile
the one paywall call: checks the feature against paywall.config.ts, counts free uses, consumes credits for credit features (server-side, before the work), publishes the wall when denied or with the use that spends an allowance or the last credits. allowed: false = stop; reason is pro / limit / credits / signin / offline; remaining and resetsAt for limits. Feature ids are typed from the config
paywallOpen
none
none
raise the upgrade prompt explicitly (manual placement)
paywallDismiss
none
none
“Maybe later” on the active wall (on the prompt or the trial-ended wall, starts the prompt cooldown)
paywallReset
none
none
dev tools: wipe local counters, dismissals, and the active wall
tabId
none
{ tabId }
content → background: the tab the script runs in, so a page renders only the walls raised there
Add the method signature to ExtensionProtocol in
apps/extension/utils/messaging.ts.
Register the handler in the owning background module, at the top
level of the file (see
background patterns).
Call it with sendMessage from any surface. The compiler enforces
payload and response types on both ends.
If the message belongs to a prunable module, wrap the protocol lines in
that module’s wiring markers (// module:<id>:start … end) so pruning
keeps the file compiling; see the module system.
State reads. Surfaces don’t ask the background for state; they read
the storage.local snapshots (user, entitlements, access,
paywall, broadcasts) via hooks (useAuth, useEntitlement,
useFeature, usePlan, usePaywall). Messages are for actions.
Backend calls with tokens. UI and content scripts never hold ID
tokens; they send a message, and the background attaches the token to
the API call.