Roles and access
packages/core/src/lib/rbac.ts is the single source of truth: ROLE_TYPES,
ROLE_PERMISSIONS, PAGE_ACCESS, plus ROLE_LABELS and ROLE_DESCRIPTIONS
for the UI.
| Role | Intended for |
|---|---|
superadmin |
Owner tier — everything, including user management and financials |
admin |
Operational admin without the owner-only financial, CDP and user controls |
finance |
Bookings and the financial dashboard; owns the money-out module |
editor |
Events, artists, content, vendor triage |
signing_staff |
Signing QR entry/exit scans, slips, signing queue |
stamp_rally_staff |
Stamp rally prize scans and handovers |
entry_staff |
Entry ticket admission and re-entry scanning |
entry_counter |
Walk-in ticket sales, payment collection, wristbands, refunds |
payment_staff |
Signing payments, receipts, event-day payment operations |
Permission model
Section titled “Permission model”A permission is a flat string (manage_events, view_bookings,
use_scanner, …). ROLE_PERMISSIONS maps each role to its list, and a user
holds the union of their roles’ permissions:
userHasPermission(roles, "manage_bookings");hasPermission("finance", "view_financials");PAGE_ACCESS maps an admin path to the permissions that open it, OR-matched
— any one of them is enough. That is deliberate: the auction winners page lists
both manage_events and view_bookings so finance can reach the mark-paid
action without being granted event management.
export const PAGE_ACCESS: Record<string, Permission[]> = { "/admin": ["view_dashboard"], "/admin/events": ["manage_events"], "/admin/bookings/entry": ["manage_entry_tickets"], // ...};Two layers, not one
Section titled “Two layers, not one”Page access is the outer layer. The inner layer is the tRPC procedure: a
roleProtectedProcedure re-checks the permission on every mutation. Some
actions are deliberately narrower than the page that hosts them — an editor can
triage a vendor application but cannot mark it paid, because mark-paid is
enforced at the procedure.
Never rely on hiding a button. Gate the procedure.
Granting roles
Section titled “Granting roles”Roles are granted through the audited users.updateRoles procedure on
/admin/users. The old sign-in auto-grant was retired on 2026-09-15; there
is no self-service path and no environment-based shortcut in production.
Adding a permission or role
Section titled “Adding a permission or role”Use the rbac-permissions skill. The moving parts are: add to Permission,
add it to each role in ROLE_PERMISSIONS, register the page in PAGE_ACCESS,
and gate the procedure.
