Skip to content

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

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"],
// ...
};

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.

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.

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.