Skip to main content

Permissions System

The template implements a granular, resource-based permissions system with type-safe permission definitions, logical groupings for UI organization, and utility functions for state management and change detection.

Architecture Overview

Source Files

FilePurpose
lib/permissions/definitions.tsPermission constants, type extraction, default roles
lib/permissions/groups.tsUI-oriented permission groupings with metadata
lib/permissions/utils.tsState management, diff calculation, and filtering

Permission Definitions

Permissions follow a resource:action naming convention. The PERMISSIONS object organizes them by resource:

export const PERMISSIONS = {
items: {
read: 'items:read',
create: 'items:create',
update: 'items:update',
delete: 'items:delete',
review: 'items:review',
approve: 'items:approve',
reject: 'items:reject',
},
categories: {
read: 'categories:read',
create: 'categories:create',
update: 'categories:update',
delete: 'categories:delete',
},
tags: {
read: 'tags:read',
create: 'tags:create',
update: 'tags:update',
delete: 'tags:delete',
},
roles: {
read: 'roles:read',
create: 'roles:create',
update: 'roles:update',
delete: 'roles:delete',
},
users: {
read: 'users:read',
create: 'users:create',
update: 'users:update',
delete: 'users:delete',
assignRoles: 'users:assignRoles',
},
analytics: {
read: 'analytics:read',
export: 'analytics:export',
},
system: {
settings: 'system:settings',
},
} as const;

Complete Permission List

ResourceActions
itemsread, create, update, delete, review, approve, reject
categoriesread, create, update, delete
tagsread, create, update, delete
rolesread, create, update, delete
usersread, create, update, delete, assignRoles
analyticsread, export
systemsettings

Type-Safe Permission Type

The Permission type is extracted from the PERMISSIONS constant using recursive conditional types:

type PermissionValues<T> = T extends Record<string, infer U>
? U extends Record<string, infer V>
? V extends string ? V : never
: never
: never;

export type Permission = PermissionValues<typeof PERMISSIONS>;
// Resolves to: 'items:read' | 'items:create' | ... | 'system:settings'

This ensures compile-time safety: any permission string that does not exist in the PERMISSIONS constant will cause a TypeScript error.

Query Functions

// Get all permissions as a flat array
export function getAllPermissions(): Permission[];

// Get permissions for a specific resource
export function getPermissionsForResource(resource: keyof typeof PERMISSIONS): Permission[];

// Validate whether a string is a valid permission
export function isValidPermission(permission: string): permission is Permission;

Default Roles

Two built-in role definitions provide starting points:

export const DEFAULT_ROLES = {
SUPER_ADMIN: {
id: 'super-admin',
name: 'Super Administrator',
description: 'Full system access with all permissions',
permissions: getAllPermissions(), // Every permission
},
CONTENT_MANAGER: {
id: 'content-manager',
name: 'Content Manager',
description: 'Manage content including items, categories, and tags',
permissions: [
...getPermissionsForResource('items'),
...getPermissionsForResource('categories'),
...getPermissionsForResource('tags'),
],
},
} as const;

Permission Groups

Groups organize permissions for UI display with icons and descriptions:

export interface PermissionGroup {
id: string;
name: string;
description: string;
icon: string; // Lucide icon name
permissions: Permission[];
}

export const PERMISSION_GROUPS: PermissionGroup[] = [
{
id: 'content',
name: 'Content Management',
description: 'Manage items, categories, and tags',
icon: 'FileText',
permissions: [...items, ...categories, ...tags],
},
{
id: 'users',
name: 'User Management',
description: 'Manage users and their roles',
icon: 'Users',
permissions: [...users, ...roles],
},
{
id: 'system',
name: 'System & Analytics',
description: 'System settings and analytics access',
icon: 'Settings',
permissions: [...analytics, ...system],
},
];

Group Query Functions

// Find which group a permission belongs to
export function getPermissionGroup(permission: Permission): PermissionGroup | undefined;

// Get all permissions in a group by group ID
export function getPermissionsByGroup(groupId: string): Permission[];

Permission Display Formatting

// Format for display: "items:approve" -> "Approve Items"
export function formatPermissionName(permission: Permission): string;

// Generate description: "items:approve" -> "Approve submissions items and submissions"
export function formatPermissionDescription(permission: Permission): string;

The description formatter uses lookup tables for both actions and resources:

ActionDescription Prefix
readView and access
createCreate new
updateEdit existing
deleteRemove
reviewReview and moderate
approveApprove submissions
rejectReject submissions
assignRolesAssign roles to
exportExport data from
settingsManage settings for

Permission State Management

The utilities module provides functions for managing permission state in the UI:

Creating State from Permissions

export function createPermissionState(currentPermissions: Permission[]): PermissionState;
// Returns: { 'items:read': true, 'items:create': true, ... }

Extracting Selected Permissions

export function getSelectedPermissions(permissionState: PermissionState): Permission[];
// Filters the state object to return only permissions where value is `true`

Change Detection

export function calculatePermissionChanges(
originalPermissions: Permission[],
newPermissions: Permission[]
): PermissionChanges;
// Returns: { added: Permission[], removed: Permission[] }

Equality Check

export function arePermissionsEqual(
permissions1: Permission[],
permissions2: Permission[]
): boolean;
// Uses Set-based comparison for order-independent equality

Search Filtering

export function filterPermissions(
permissions: Permission[],
searchTerm: string
): Permission[];
// Matches against permission string and space-separated format
// e.g., "assign" matches "users:assignRoles" and "users assignRoles"

Usage Example

import { PERMISSIONS, getAllPermissions } from '@/lib/permissions/definitions';
import { PERMISSION_GROUPS, formatPermissionName } from '@/lib/permissions/groups';
import { createPermissionState, calculatePermissionChanges } from '@/lib/permissions/utils';

// Check a specific permission
if (userPermissions.includes(PERMISSIONS.items.approve)) {
// User can approve items
}

// Build a permission editor UI
const state = createPermissionState(user.permissions);

// After user toggles permissions
const changes = calculatePermissionChanges(user.permissions, newPermissions);
console.log(`Added: ${changes.added.length}, Removed: ${changes.removed.length}`);