Role Hierarchy
Three roles are defined in theuser_role enum (see lib/db/schema.ts:60-64):
User (Default)
Standard platform access:- Create and edit own projects
- Register for events
- Send team invites
- Update profile settings
- No access to
/adminroutes
Moderator
Content moderation capabilities:- All user permissions
- Access to
/admin/dashboard,/admin/users - Hide/unhide users
- Ban users (excluding admins)
- View audit log (read-only in some implementations)
Moderators cannot ban users with
admin role, change roles, or access admin-only pages like /admin/roles, /admin/audit, and /admin/mentors.Admin
Full platform control:- All moderator permissions
- Unban users
- Delete user accounts
- Assign roles (user, moderator, admin)
- Review mentor applications
- Access audit log
- Manage Sanity Studio at
/studio
Super Admin
A special admin designation granted via environment variable:- Clerk user IDs listed in
ADMIN_USER_IDSare always treated as admins - Bypasses database
rolecolumn (source of truth for super-admins) - Cannot be banned, hidden, deleted, or have role changed
- Identified by
isSuperAdmin(clerkUserId)helper inlib/admin.ts:10-12
Role Assignment
Access Requirements
Requires
admin role to access /admin/rolesapp/admin/roles/page.tsx:11).
Role Management Interface
Navigate to/admin/roles to view and manage elevated users:
- Displays all users with
moderatororadminrole - Search by display name or username
- Shows super-admin badge for
ADMIN_USER_IDSentries - Change role via dropdown or promote users by search
Set User Role
Server action inapp/admin/roles/actions.ts:14-74:
- Verify actor has admin permissions via
isAdmin(userId) - Fetch target user profile
- Check restrictions:
- Cannot change super-admin roles
- Cannot change your own role
- No-op if same role
- Update
profiles.rolein database - Log to
adminAuditLogwitholdRoleandnewRolemetadata - Revalidate
/admin/rolesand/admin/userspaths
Search for Users
Server actionsearchProfilesByName(query: string) (lines 76-110):
- Minimum 2 characters required
- Case-insensitive search on
displayNameandusernameusingILIKE - Returns up to 10 results
- Used for quick user lookup when assigning roles
Permission Helpers
Role checks are centralized inlib/admin.ts:
isSuperAdmin(clerkUserId)
Synchronous check against ADMIN_USER_IDS env var (lines 10-12):
getRole(clerkUserId)
Async helper that returns the effective role (lines 14-25):
"user" if profile doesn’t exist (e.g., during lazy profile creation).
isAdmin(clerkUserId)
Async check for admin permissions (lines 27-30):
isModerator(clerkUserId)
Async check for moderator or admin permissions (lines 32-35):
isModerator returns true for admins since admins inherit all moderator capabilities.canAccessAdmin(clerkUserId)
Alias for isModerator (lines 37-39):
/admin/* routes.
Session Helper
lib/admin/get-admin-session.ts provides a React cache-wrapped session getter:
null if unauthenticated or insufficient permissions. Used in admin pages and layouts to conditionally render UI based on role.
Middleware Protection
Route guards inapp/proxy.ts:
Admin Layout Navigation
The admin layout (app/admin/layout.tsx) conditionally renders nav links based on session role:
- All moderators/admins: Dashboard, Users
- Admins only: Roles, Mentors, Audit Log
Permissions Matrix
| Action | User | Moderator | Admin | Super Admin |
|---|---|---|---|---|
Access /admin/dashboard | No | Yes | Yes | Yes |
Access /admin/users | No | Yes | Yes | Yes |
| Hide/unhide users | No | Yes | Yes | Yes |
| Ban users (non-admin) | No | Yes | Yes | Yes |
| Ban admins | No | No | Yes | Yes |
| Unban users | No | No | Yes | Yes |
| Delete users | No | No | Yes | Yes |
Access /admin/roles | No | No | Yes | Yes |
| Assign roles | No | No | Yes | Yes |
Access /admin/mentors | No | No | Yes | Yes |
Access /admin/audit | No | No | Yes | Yes |
Access /studio | No | No | Yes | Yes |
| Be banned/deleted | Yes | Yes | Yes | No |
| Have role changed | Yes | Yes | Yes | No |
Database Schema
Role is stored in theprofiles table (from lib/db/schema.ts:92):
adminAuditLog table with before/after values.
Best Practices
- Use moderator role for content moderation volunteers who don’t need full admin access
- Reserve admin role for trusted maintainers who manage roles and review applications
- Grant super-admin sparingly — only for platform owners who need immutable admin access
- Audit role changes regularly via
/admin/auditto track permission escalations - Never share super-admin Clerk IDs publicly or in client-side code (env var is server-only)