Add a New Paid Feature
Gate a feature behind a billing capability so only workspaces on the right plan can use it, enforced server-side and reflected in the UI.
A paid feature is gated by a billing capability (what the workspace bought), usually combined with a role check. This recipe ties together capabilities, permissions, and the billing catalog.
1. Define the capability
Add the key to permissions/capabilities.ts and its validator in
permissions/validators.ts:
export const capabilityKeys = [/* ...existing */, "feature.exports"] as const;2. Attach it to the paid plans
In billing/plans.config.ts, add the capability to every plan that should
include it:
pro_monthly: { /* ... */ capabilityKeys: [/* ... */, "feature.exports"] },Workspaces with an active grant for those plans now resolve the capability. See Entitlements and Grants.
3. Require it on the action
Add an app permission rule that requires the capability (and a role statement), then enforce it server-side:
// permissions/appPermissions.ts
"report.export": {
betterAuth: { billing: ["read"] },
capabilities: ["feature.exports"],
},await requireAppPermission(ctx, { permission: "report.export" });A workspace on a plan without the capability is denied — even if the member's role would otherwise allow the action.
4. Gate and upsell in the UI
const canExport = useAppPermission("report.export");
// show the feature, or an upgrade prompt linking to /settings/billingHiding the button is convenience; the backend capability check is the boundary.
5. Track usage (optional)
Emit the paid_feature_used analytics event at the feature seam so you can
measure adoption — see Analytics (PostHog).
Related
- Add a New Billing Plan — create the plan that includes this capability.
- Add a New Permission — role-only gating.