RBAC Permissions
Configure role-based access control for admin panel features and actions.
MakerKit's admin RBAC system controls what each admin role can do. By default, a single admin role has full permissions. You can add custom roles (support, moderator) with restricted access by editing packages/rbac/src/admin-rbac.config.ts. Permissions are enforced server-side via withAdminPermission() middleware and client-side via the useAdminPermissions hook.
Use singular resource names (
user,session) not plural. This matches Better Auth's internal permission model.
Default Configuration
Out of the box, MakerKit provides a single admin role with full permissions:
import { defineAdminRBACConfig } from './admin/factory';export default defineAdminRBACConfig({ // Default: admin role with all permissions});Resources and Actions
Built-in Resources
These resources are controlled by Better Auth and must use exact names:
| Resource | Actions | Description |
|---|---|---|
user | create, list, get, update, set-role, set-password, ban, impersonate, delete | User management |
session | list, revoke, delete | Session management |
Custom Resources
These resources are MakerKit-specific:
| Resource | Actions | Description |
|---|---|---|
organizations | list, view | Organization oversight |
dashboard | view | Admin dashboard access |
subscriptions | list | Subscription viewing |
Adding Custom Roles
Create restricted admin roles for support staff, moderators, or other team members:
import { defineAdminRBACConfig } from './admin/factory';export default defineAdminRBACConfig({ // Role hierarchy - higher number = more authority roles: { support: 50, // Customer support staff moderator: 30, // Community moderators }, // Define permissions for each custom role // Note: 'admin' automatically gets ALL permissions permissions: { support: { user: ['list', 'get', 'ban'], session: ['list', 'revoke'], organizations: ['list', 'view'], dashboard: ['view'], subscriptions: ['list'], }, moderator: { user: ['list', 'get', 'ban'], dashboard: ['view'], }, },});Role Hierarchy
Roles are assigned numeric values indicating authority level:
admin: 100- Full access (built-in, cannot be changed)support: 50- Mid-level accessmoderator: 30- Basic access
Higher values indicate more authority. This is useful for permission inheritance patterns if you extend the system.
Adding Custom Resources
Extend the RBAC system with your own resources:
export default defineAdminRBACConfig({ // Add custom resources resources: { REPORTS: 'reports', CONTENT: 'content', }, // Add custom actions actions: { EXPORT: 'export', PUBLISH: 'publish', MODERATE: 'moderate', }, // Define which actions are valid for each resource accessController: { reports: ['list', 'view', 'export'], content: ['list', 'view', 'publish', 'moderate'], }, // Assign permissions to roles permissions: { support: { reports: ['list', 'view'], content: ['list', 'view'], }, moderator: { content: ['list', 'view', 'moderate'], }, },});Server-Side Protection
Protecting Server Actions
Use withAdminPermission middleware to enforce permissions:
'use server';import { adminActionClient, withAdminPermission } from '@kit/action-middleware';// Require specific permissionexport const banUserAction = adminActionClient .use(withAdminPermission({ user: ['ban'] })) .inputSchema(banUserSchema) .action(async ({ parsedInput, ctx }) => { // Only admins with 'user:ban' permission reach here await banUser(parsedInput.userId); });// Require multiple permissionsexport const manageUserAction = adminActionClient .use(withAdminPermission({ user: ['get', 'ban'], session: ['revoke'], })) .inputSchema(manageUserSchema) .action(async ({ parsedInput, ctx }) => { // Requires ALL specified permissions });Protecting Data Loaders
Use requireAdmin for server components and data loaders:
import 'server-only';import { cache } from 'react';import { requireAdmin } from '@kit/auth/require-admin';export const loadReportsData = cache(async () => { await requireAdmin(); // Redirects if not admin return await reportsService.getAll();});Client-Side Permission Checks
useAdminPermissions Hook
Conditionally render UI based on permissions:
'use client';import { useAdminPermissions } from '@kit/admin/hooks/use-admin-permissions';function UserActions({ userId }: { userId: string }) { const { hasPermission, isLoading } = useAdminPermissions(); if (isLoading) return <Spinner />; return ( <div> {hasPermission({ user: ['get'] }) && ( <Button>View User</Button> )} {hasPermission({ user: ['ban'] }) && ( <Button>Ban User</Button> )} {hasPermission({ user: ['delete'] }) && ( <Button variant="destructive">Delete User</Button> )} </div> );}Hook API Reference
const { role, // Current user's admin role (or null) isAdmin, // Whether user has any admin role isLoading, // Whether session is loading hasPermission, // Check specific permission(s) hasAnyPermission,// Check if user has any permission on resource permissions, // All permissions for current role getActionsFor, // Get all actions user can perform on resource} = useAdminPermissions();// Check single permissionhasPermission({ user: ['ban'] });// Check multiple permissions (all required)hasPermission({ user: ['ban', 'delete'] });// Check any permission on resourcehasAnyPermission('user'); // true if user has any user permission// Get all available actionsgetActionsFor('user'); // ['list', 'get', 'ban', ...]Role Checking Utilities
For simple admin role checks without permission granularity:
// Client-sideimport { isAdminRole } from '@kit/auth/is-admin-role';function showAdminLink(userRole: string | null) { return isAdminRole(userRole);}// Server-side (non-redirecting)import { isUserAdmin } from '@kit/auth/require-admin';export async function getPageData() { const isAdmin = await isUserAdmin(); return { showAdminFeatures: isAdmin };}Permission Matrix
Default permissions for the built-in admin role:
| Resource | Actions |
|---|---|
user | create, list, get, update, set-role, set-password, ban, impersonate, delete |
session | list, revoke, delete |
organizations | list, view |
dashboard | view |
subscriptions | list |
Better Auth Integration
The RBAC system integrates with Better Auth's admin plugin. Important naming conventions:
- Resources must be singular:
usernotusers,sessionnotsessions - Actions must match Better Auth names:
set-rolenotchange-role,banfor both ban and unban
This ensures server actions work correctly with Better Auth's internal permission checks.
Frequently Asked Questions
Why must I use singular resource names like 'user' instead of 'users'?
Does the admin role need explicit permissions?
How do I check permissions in a React component?
Can I create permissions for custom resources?
What's the difference between requireAdmin and withAdminPermission?
Previous: Organization Management
Next: Extending Admin