Add a New Permission
Add an app permission key that gates a product action on a member's role, and optionally on a billing capability.
Use an app permission when access depends on a member's role in the workspace. If access depends on what the workspace has paid for, add a capability instead (or as well). The full conceptual guide is Roles and Permissions; this is the short path.
1. Add the catalog entry
In packages/backend/convex/permissions/policy.ts, add one entry to
permissionCatalog:
"report.export": {
roles: ["owner", "admin"],
betterAuth: { report: ["export"] },
capabilities: ["feature.pro"], // optional billing gate
resourcePolicy: "organizationMustBeActive", // optional context check
},roles decides who may take the action. betterAuth declares the statement
Better Auth uses to represent that grant. The catalog derives the Better Auth
role objects, app-permission key list, and Convex validator; do not edit any of
those generated projections separately.
If the statement uses a new Better Auth resource or action, first add that
resource/action to organizationStatements in the same policy module. Add a
capability only when access depends on an organization entitlement, and add a
resource policy only when access depends on a target or organization state.
2. Enforce it in Convex
import { requireAppPermission } from "./permissions";
const actor = await requireAppPermission(ctx, { permission: "report.export" });This single call verifies authentication, membership, role, capabilities, and any resource policy — there is no per-call copy-paste.
3. Reflect it in the UI (visibility only)
const canExport = useAppPermission("report.export");Hiding UI is convenience; the backend check is the boundary.
4. Test it
Add or extend cases near packages/backend/convex/permissions/appPermissions.test.ts.
The role-by-permission parity test must continue to pass: each catalog grant
must match the generated Better Auth role's authorization result.
Related
- Permissions reference — every key and its mapping.
- Capabilities — gating on billing instead of role.