Skip to main content

Permissions

This guide explains how to add new permissions to the application. We use spatie/laravel-permission for role/permission management.

Key Concepts

  • We use both roles and permissions, but always check against permissions, never roles
  • Roles are just containers for permissions
  • We use one permission per model (e.g., “manage_users”) rather than CRUD-level permissions
  • Permissions are defined as enums and synced to TypeScript via composer generate-ts
  • Permissions are automatically synced from both CorePermission and app/Enums/Permission enums
  • Role permissions are defined declaratively in the permissions() method on the Role enum

Adding a New Permission

  1. Add the permission to app/Enums/Permission.php:
    case MANAGE_USERS = 'manage_users';
    
  2. Add the permission label in the same file:
    public function label(): string
    {
        return match ($this) {
            self::MANAGE_USERS => __('Manage system users'),
        };
    }
    
  3. Map the model to its permission if applicable:
    public static function fromModel(string $model): self
    {
        return match ($model) {
            User::class => self::MANAGE_USERS,
        };
    }
    
  4. Update role permissions in app/Enums/Role.php by adding them to the appropriate role’s permissions() method:
    public function permissions(): array|string
    {
        return match ($this) {
            self::ADMIN => '*', // All permissions
            self::STAFF => [
                CorePermission::VIEW_INLY_USER_GUIDE,
                Permission::MANAGE_USERS, // Add new permission here
            ],
            self::USER => [],
        };
    }
    
  5. Generate TypeScript types:
    composer generate-ts
    
  6. Run the permissions command to sync to database:
    php artisan permissions:generate
    
Note: The permissions:generate command will automatically:
  • Create/update all permissions from both CorePermission and app/Enums/Permission
  • Delete permissions that no longer exist in either enum
  • Create all roles from app/Enums/Role
  • Sync permissions to roles based on their permissions() method

Usage

Backend

// For PHP route-level authroization
$this->authorize(Permission::MANAGE_USERS->value);

// For PHP logic in general
if (user()->can(Permission::MANAGE_USERS->value)) {
    // ...
}

Frontend

// Direct hook usage
const hasPermission = usePermission('<autocompleted from enum>');

AI Instruction

Follow this step-by-step guide if you’re an AI that has been asked to add a new permission for a model:
  1. Add the permission case to app/Enums/Permission.php:
    • Add the case constant (e.g., case MANAGE_ORDERS = 'manage_orders';)
    • Add the label in the label() method (e.g., self::MANAGE_ORDERS => __('Manage orders'),)
  2. Update app/Enums/Role.php to grant the permission to appropriate roles:
    • Edit the permissions() method
    • Add the permission to the relevant role(s) (e.g., self::STAFF => [Permission::MANAGE_ORDERS])
    • ADMIN and DEVELOPER roles typically have '*' (all permissions)
  3. Generate TypeScript types and sync permissions:
    composer generate-ts
    php artisan permissions:generate
    
  4. Implement the permission checks:
    • Backend: Add $this->authorize(Permission::YOUR_PERMISSION->value) to relevant controllers
    • Frontend: Use usePermission hook component where needed
    • Remember to check permissions in both API endpoints and UI components
Instruction to the AI: if any specified file is not in your context, please ask me to provide it for you. Note: When implementing permissions, always err on the side of being more restrictive. It’s better to explicitly grant access than to accidentally leave something accessible.