# Permissions API Reference

> Available resources, actions, and permission checking APIs.

*Canonical: https://makerkit.dev/docs/tanstack-drizzle/members-management/permissions-api*

---

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`:

```typescript
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.

```typescript
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.

```typescript
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`.

```typescript
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;
```

| Resource | Description |
|----------|-------------|
| `organization` | Organization settings and deletion |
| `member` | Member management |
| `invitation` | Member invitations |
| `billing` | Subscription and payment management |
| `ac` | Access control configuration |

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

```typescript
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`.

```typescript
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`:

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

## Role Hierarchy

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

```typescript
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`:

```typescript
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.

```typescript
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', 'cancel'],
    ac: ['read'],
    billing: ['read'],
  },
} as const;
```

## Permission Functions

### canTargetRole

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

```typescript
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.

```typescript
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.

```typescript
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:

```typescript
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:

```typescript
import { canTargetRole } from '@kit/rbac';

// In a server function 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:

```typescript
import { auth } from '@kit/better-auth';
import { getRequestHeaders } from '@tanstack/react-start/server';

const { success } = await auth.api.hasPermission({
  headers: getRequestHeaders(),
  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 Function Authorization Middleware

Middleware for declarative authorization in server functions. Apply a pre-composed
middleware tuple to a literal `createServerFn(...)` call via the native
`.middleware([...])` step, then continue with `.validator(schema)` and
`.handler(...)`.

### withMinRole

Checks minimum role level using the hierarchy.

```typescript
import { createServerFn } from '@tanstack/react-start';

import { authFunctionMiddleware } from '@kit/function-middleware/functions';
import { withMinRole } from '@kit/function-middleware/server';

export const deleteOrgFunction = createServerFn({ method: 'POST' })
  .middleware([...authFunctionMiddleware, withMinRole('owner')])
  .validator(DeleteOrgSchema)
  .handler(async ({ data, context }) => {
    // Only owners can reach here
    // context.organizationId and context.role are available
  });
```

### withFeaturePermission

Checks permissions using Better Auth's permission system.

```typescript
import { createServerFn } from '@tanstack/react-start';

import { authFunctionMiddleware } from '@kit/function-middleware/functions';
import { withFeaturePermission } from '@kit/function-middleware/server';

export const manageBillingFunction = createServerFn({ method: 'POST' })
  .middleware([...authFunctionMiddleware, withFeaturePermission({ billing: ['update'] })])
  .validator(BillingSchema)
  .handler(async ({ data, context }) => {
    // Only users with billing:update permission can reach here
  });
```

### organizationFunctionMiddleware

Pre-composed middleware tuple that injects organization context (no extra
permission checks).

```typescript
import { createServerFn } from '@tanstack/react-start';

import { organizationFunctionMiddleware } from '@kit/function-middleware/functions';

export const listMembersFunction = createServerFn({ method: 'POST' })
  .middleware(organizationFunctionMiddleware)
  .validator(ListMembersSchema)
  .handler(async ({ data, context }) => {
    const { organizationId, role } = context;
    // List members for the organization
  });
```

## UI Permission Checks

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

```typescript
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:Action | owner | admin | member |
|-----------------|-------|-------|--------|
| organization:update | Yes | Yes | No |
| organization:delete | Yes | No | No |
| member:create | Yes | Yes | No |
| member:update | Yes | Yes | No |
| member:delete | Yes | Yes | No |
| invitation:create | Yes | Yes | Limited* |
| invitation:cancel | Yes | Yes | Yes |
| billing:read | Yes | Yes | Yes |
| billing:update | Yes | Yes | No |
| billing:delete | Yes | Yes | No |
| ac:create/update/delete | Yes | No | No |
| ac:read | Yes | Yes | Yes |

*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`:

```typescript
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.

This permissions system is part of the [TanStack Start Drizzle SaaS Kit](/drizzle).
