Permissions API Reference

Available resources, actions, and permission checking APIs.

The RBAC system uses a two-tier permission model:

  1. Hierarchy-based permissions - Role levels determine which roles can manage other roles
  2. Feature-specific permissions - Resource/action pairs grant access to specific features

Configuration

All RBAC settings are configured in packages/rbac/src/rbac.config.ts using defineRBACConfig:

import { defineRBACConfig } from './core/factory';
export default defineRBACConfig({
resources: { PROJECT: 'project' },
actions: { ARCHIVE: 'archive' },
roles: { moderator: 30 },
accessController: {
project: ['create', 'read', 'update', 'delete', 'archive'],
},
permissions: {
owner: { project: ['create', 'read', 'update', 'delete', 'archive'] },
admin: { project: ['create', 'read', 'update'] },
member: { project: ['read'] },
},
});

Type Definitions

Role

A typed union of role names from the role hierarchy.

import { type Role } from '@kit/rbac';
// Role = 'owner' | 'admin' | 'member' (plus any custom roles)
function setRole(role: Role) {
// TypeScript provides autocomplete
}

DefaultRole

Alias for Role type, kept for backward compatibility.

import { type DefaultRole } from '@kit/rbac';
// DefaultRole = Role = 'owner' | 'admin' | 'member'

Resources

Resources represent entities that can be protected by permissions. Default resources are included automatically; add custom resources in rbac.config.ts.

import { RESOURCES } from '@kit/rbac';
// Default resources (always available)
const RESOURCES = {
ORGANIZATION: 'organization',
MEMBER: 'member',
INVITATION: 'invitation',
BILLING: 'billing',
AC: 'ac', // Access Control
// Custom resources from rbac.config.ts are merged here
} as const;
ResourceDescription
organizationOrganization settings and deletion
memberMember management
invitationMember invitations
billingSubscription and payment management
acAccess control configuration

To add custom resources, edit packages/rbac/src/rbac.config.ts:

export default defineRBACConfig({
resources: {
PROJECT: 'project',
TASK: 'task',
},
});

Actions

Actions represent operations that can be performed on resources. Default actions are included automatically; add custom actions in rbac.config.ts.

import { ACTIONS } from '@kit/rbac';
// Default actions (always available)
const ACTIONS = {
CREATE: 'create',
READ: 'read',
UPDATE: 'update',
DELETE: 'delete',
CANCEL: 'cancel',
// Custom actions from rbac.config.ts are merged here
} as const;

To add custom actions, edit packages/rbac/src/rbac.config.ts:

export default defineRBACConfig({
actions: {
ARCHIVE: 'archive',
PUBLISH: 'publish',
EXPORT: 'export',
},
});

Role Hierarchy

Roles are assigned numeric hierarchy levels. Higher values indicate more permissions.

import { ROLE_HIERARCHY } from '@kit/rbac';
// Default roles (always available)
const ROLE_HIERARCHY = {
owner: 100, // Can manage all roles
admin: 50, // Can manage members
member: 10, // Cannot manage anyone
// Custom roles from rbac.config.ts are merged here
} as const;

The hierarchy determines which roles can manage other roles. A role can only manage roles with a lower hierarchy level.

To add custom roles, edit packages/rbac/src/rbac.config.ts:

export default defineRBACConfig({
roles: {
moderator: 30, // Between admin (50) and member (10)
viewer: 5, // Below member
},
});

Role Permissions

Role permissions map each role to its allowed resource/action combinations.

import { DEFAULT_ROLE_PERMISSIONS } from '@kit/rbac';
// Structure: role -> resource -> actions[]
const DEFAULT_ROLE_PERMISSIONS = {
owner: {
organization: ['update', 'delete'],
member: ['create', 'update', 'delete'],
invitation: ['create', 'cancel'],
ac: ['create', 'read', 'update', 'delete'],
billing: ['read', 'update', 'delete'],
},
admin: {
organization: ['update'],
member: ['create', 'update', 'delete'],
invitation: ['create', 'cancel'],
ac: ['read'],
billing: ['read', 'update', 'delete'],
},
member: {
organization: [],
member: [],
invitation: ['create'],
ac: [],
billing: ['read'],
},
} as const;

Permission Functions

canTargetRole

Checks if one role can manage another based on hierarchy levels.

import { canTargetRole } from '@kit/rbac';
// Admin (50) can target member (10)
canTargetRole('admin', 'member');
// Returns: true
// Admin (50) cannot target admin (50) by default
canTargetRole('admin', 'admin');
// Returns: false
// With allowEqual, same-level targeting is permitted
canTargetRole('admin', 'admin', true);
// Returns: true
// With custom role hierarchy (for Dynamic Access Control)
const customHierarchy = { supervisor: 40 };
canTargetRole('admin', 'supervisor', false, customHierarchy);
// Returns: true

The allowEqual parameter controls whether a role can target roles at the same hierarchy level. This is useful for invitations where an admin should be able to invite other admins.

getRoleHierarchy

Gets the numeric hierarchy level for a role.

import { getRoleHierarchy } from '@kit/rbac';
getRoleHierarchy('owner'); // Returns: 100
getRoleHierarchy('admin'); // Returns: 50
getRoleHierarchy('member'); // Returns: 10
// With custom role hierarchy
const customHierarchy = { supervisor: 40 };
getRoleHierarchy('supervisor', customHierarchy); // Returns: 40

Utility Functions

Additional helpers for working with roles.

import {
getCreatorRole,
getDefaultRole,
getRolesSortedByHierarchy,
getAllDefaultRoles,
} from '@kit/rbac';
// Get the highest-level role (organization creator)
getCreatorRole();
// Returns: 'owner'
// Get the lowest-level role (default for new members)
getDefaultRole();
// Returns: 'member'
// Get roles sorted by hierarchy (highest first)
getRolesSortedByHierarchy();
// Returns: ['owner', 'admin', 'member']
// Get all role names
getAllDefaultRoles();
// Returns: ['owner', 'admin', 'member']

All these functions accept an optional customRoleHierarchy parameter for Dynamic Access Control scenarios:

const customHierarchy = { supervisor: 40 };
getRolesSortedByHierarchy(customHierarchy);
// Returns: ['owner', 'admin', 'supervisor', 'member']

Server-side Permission Checking

Using canTargetRole for Member Management

For checking if a user can manage another member:

import { canTargetRole } from '@kit/rbac';
// In a server action or API route
const canManage = canTargetRole(
currentUserRole, // Role of the user performing the action
targetMemberRole, // Role of the member being managed
false, // Don't allow same-level targeting
customRoleHierarchy, // Optional: for Dynamic Access Control
);
if (!canManage) {
throw new Error('Cannot manage members with equal or higher roles');
}

Using Better Auth's Permission API

For checking resource/action permissions:

import { auth } from '@kit/better-auth';
import { headers } from 'next/headers';
const { success } = await auth.api.hasPermission({
headers: await headers(),
body: {
permissions: {
billing: ['update'],
},
},
});
if (!success) {
throw new Error('Permission denied');
}

The permissions object maps resources to an array of required actions. The check passes if the user has all specified permissions.

Server Action Authorization Middleware

Middleware functions for declarative authorization in server actions using the .use() pattern.

withMinRole

Checks minimum role level using the hierarchy.

import { authenticatedActionClient, withMinRole } from '@kit/action-middleware';
export const deleteOrgAction = authenticatedActionClient
.use(withMinRole('owner'))
.inputSchema(DeleteOrgSchema)
.action(async ({ parsedInput, ctx }) => {
// Only owners can reach here
// ctx.organizationId and ctx.role are available
});

withFeaturePermission

Checks permissions using Better Auth's permission system.

import { authenticatedActionClient, withFeaturePermission } from '@kit/action-middleware';
export const manageBillingAction = authenticatedActionClient
.use(withFeaturePermission({ billing: ['update'] }))
.inputSchema(BillingSchema)
.action(async ({ parsedInput, ctx }) => {
// Only users with billing:update permission can reach here
});

organizationActionClient

Pre-configured client with organization context (no permission checks).

import { organizationActionClient } from '@kit/action-middleware';
export const listMembersAction = organizationActionClient
.inputSchema(ListMembersSchema)
.action(async ({ parsedInput, ctx }) => {
const { organizationId, role } = ctx;
// List members for the organization
});

UI Permission Checks

For UI components, use canTargetRole directly from @kit/rbac:

'use client';
import { canTargetRole } from '@kit/rbac';
function MemberActions({
currentUserRole,
targetMemberRole,
roleConfig,
}) {
// Check if current user can manage this member
const canManage = canTargetRole(
currentUserRole,
targetMemberRole,
false,
roleConfig.roleHierarchy, // Custom role hierarchy if using DAC
);
if (!canManage) {
return null;
}
return <EditMemberButton />;
}

Capability Matrix

Default permission assignments for built-in roles:

Resource:Actionowneradminmember
organization:updateYesYesNo
organization:deleteYesNoNo
member:createYesYesNo
member:updateYesYesNo
member:deleteYesYesNo
invitation:createYesYesLimited*
invitation:cancelYesYesNo
billing:readYesYesYes
billing:updateYesYesNo
billing:deleteYesYesNo
ac:create/update/deleteYesNoNo
ac:readYesYesNo

*Members can create invitations but cannot invite higher roles (enforced via canTargetRole).

Access Controller

The ac object is the Better Auth access controller, built from rbac.config.ts:

import { ac, buildAccessController } from '@kit/rbac/access-controller';
// Use the pre-built instance
ac.newRole({ project: ['read', 'update'] });
// Or build a fresh instance
const customAc = buildAccessController();

The access controller is automatically synchronized with your rbac.config.ts settings. When you add custom resources, actions, or permissions, they are immediately available in Better Auth's authorization system.