techieni3 / laravel-user-permissions
A Laravel package for managing user permissions.
Installs: 13
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/techieni3/laravel-user-permissions
Requires
- php: ^8.4
- laravel/framework: ^v12.35.0
Requires (Dev)
- laravel/pint: ^1.25
- orchestra/testbench: ^10.7
- pestphp/pest: ^4.0
- phpstan/phpstan: ^2.1
- rector/rector: ^2.2
README
Laravel User Permissions
A Laravel package for managing user roles and permissions. It uses PHP enums for type-safe role definitions and provides a web dashboard for managing access control. Ideally suited for applications that require a robust, easy-to-manage permission system with a visual interface.
Table of Contents
- Requirements
- Installation
- Quick Start
- Configuration
- Dashboard
- Working with Roles
- Working with Permissions
- Laravel Gate Integration
- Middleware
- Model Actions
- Cascade Deletion
- Events
- Model Discovery Configuration
- Caching
- Testing
- Similar Packages
- Security
- License
Requirements
- PHP 8.4+
- Laravel 12.35.0+
Installation
Install the package via Composer:
composer require techieni3/laravel-user-permissions
Run the installation command to set up the package:
php artisan install:permissions
This command will:
- Publish the config file to
config/permissions.php - Publish migrations to
database/migrations/ - Publish
Role.phpModelActions.phpenums toapp/Enums - Add the
HasRolestrait to your User model
Run the migrations:
php artisan migrate
Quick Start
1. Define Your Roles
Edit app/Enums/Role.php:
<?php namespace App\Enums; enum Role: string { case Admin = 'admin'; case Editor = 'editor'; case User = 'user'; }
2. Sync Roles to Database
php artisan sync:roles
3. Generate Permissions
The package automatically generates permissions for your models based on actions defined in app/Enums/ModelActions.php:
php artisan sync:permissions
This creates permissions like post.create, post.update, user.view_any, etc.
4. Assign Roles and Permissions
use App\Enums\Role; // Assign a role $user->addRole(Role::Admin); // Assign permission directly $user->addPermission('post.create'); // Check permissions if ($user->hasPermission('post.create')) { // User can create posts } // Check roles if ($user->hasRole(Role::Admin)) { // User is an admin }
Configuration
The config file is located at config/permissions.php:
return [ 'dashboard' => [ 'enabled' => true, 'prefix' => 'permissions-manager', 'middleware' => ['web', 'auth'], 'gate' => 'viewPermissionsDashboard', 'user_display_column' => 'name', ], 'models' => [ 'directory' => app_path('Models'), 'excluded' => [ // App\Models\User::class, ], ], 'classes' => [ 'user' => App\Models\User::class, 'role_enum' => App\Enums\Role::class, 'model_actions_enum' => App\Enums\ModelActions::class, ], 'events_enabled' => true, ];
Dashboard Configuration
enabled - Enable or disable the web dashboard (default: true)
prefix - URL path for the dashboard (default: permissions-manager)
middleware - Middleware applied to dashboard routes (default: ['web', 'auth'])
gate - Gate name for authorizing dashboard access (default: viewPermissionsDashboard). Set to null to disable authorization.
user_display_column - Database column to display as username in the dashboard (default: name)
Model Discovery
included - Array of directory paths and/or specific model classes to include for permission generation. Can contain:
- Directory paths (e.g.,
app_path('Models')) - scans all models in the directory - Specific model class strings (e.g.,
App\External\CustomModel::class) - includes individual models
excluded - Array of model classes to skip during permission generation
Core Classes
user - Your application's User model class
role_enum - The enum class defining your roles
model_actions_enum - The enum class defining permission actions
Events
events_enabled - Enable or disable event dispatching when roles/permissions change (default: true)
Dashboard
Access the dashboard at /permissions-manager (or your configured prefix).
Protecting Dashboard Access
Define a gate in your AppServiceProvider:
use Illuminate\Support\Facades\Gate; public function boot(): void { Gate::define('viewPermissionsDashboard', function (User $user) { return $user->hasRole('admin'); }); }
To disable gate authorization, set 'gate' => null in the config.
Dashboard Features
- Users Page: List all users, search, and view their roles/permissions
- Roles Page: List all roles and their assigned permissions
- User Detail: Assign/remove roles and permissions for a specific user
- Role Detail: Manage which permissions are assigned to a role
Screenshots
Dashboard Troubleshooting
Dashboard 404 Not Found
If you are unable to access the dashboard at /permissions-manager:
- Ensure you have run
php artisan migrate. - Clear your route cache:
php artisan route:clear. - Verify the
prefixinconfig/permissions.php. - Make sure a login route exists, as the dashboard is protected by the
authmiddleware by default.
Dashboard 500 Error
If you encounter a 500 Internal Server Error when accessing the dashboard:
- Check that the
viewPermissionsDashboardgate is defined in yourAppServiceProvider(or use the custom gate name from your config). - Alternatively, set
'gate' => nullinconfig/permissions.phpto disable gate authorization. - Check your Laravel logs at
storage/logs/laravel.logfor specific error details.
Assets Not Loading
The dashboard uses inline styles and scripts. You do not need to publish assets. If the dashboard is blank:
- Check your browser console for JavaScript errors.
- Ensure
APP_URLin your.envfile is set correctly.
Working with Roles
Adding Roles
use App\Enums\Role; // Add a single role $user->addRole(Role::Admin); // Add multiple roles $user->syncRoles([Role::Admin, Role::Editor]);
Removing Roles
// Remove a role $user->removeRole(Role::Admin); // Remove all roles and sync new ones $user->syncRoles([Role::User]);
Checking Roles
// Check a single role if ($user->hasRole(Role::Admin)) { // User is an admin } // Check any role if ($user->hasAnyRole([Role::Admin, Role::Editor])) { // User has at least one of these roles } // Check all roles if ($user->hasAllRoles([Role::Admin, Role::Editor])) { // User has both roles }
Query Users by Role
// Get all admins $admins = User::role(Role::Admin)->get(); // Get users with multiple roles $editors = User::role([Role::Admin, Role::Editor])->get(); // Get users without a role $nonAdmins = User::withoutRole(Role::Admin)->get();
Working with Permissions
Adding Permissions
// Add a single permission $user->addPermission('post.create'); // Sync multiple permissions $user->syncPermissions(['post.create', 'post.update', 'post.delete']);
Removing Permissions
// Remove a permission $user->removePermission('post.create'); // Remove all permissions and sync new ones $user->syncPermissions(['post.view']);
Checking Permissions
// Check single permission if ($user->hasPermission('post.create')) { // User can create posts } // Using alias if ($user->hasPermissionTo('post.create')) { // Same as hasPermission() } // Check any permission if ($user->hasAnyPermission(['post.create', 'post.update'])) { // User has at least one permission } // Check all permissions if ($user->hasAllPermissions(['post.create', 'post.update'])) { // User has all permissions }
Query Users by Permission
// Get users with permission $creators = User::permission('post.create')->get(); // Get users with multiple permissions $editors = User::permission(['post.create', 'post.update'])->get(); // Get users without permission $viewers = User::withoutPermission('post.create')->get();
Understanding Direct vs Role Permissions
Users can have permissions in two ways:
- Direct permissions: Assigned directly to the user
- Role permissions: Inherited from assigned roles
// Get all permissions (direct + role-based) $allPermissions = $user->getPermissions(); // Get only direct permissions $directPermissions = $user->directPermissions;
Laravel Gate Integration
All permissions are automatically registered with Laravel's authorization system:
// Using Gate facade if (Gate::allows('post.create')) { // Current user can create posts } // Using User model if ($user->can('post.create')) { // User can create posts } // In Blade templates @can('post.create') <button>Create Post</button> @endcan
Middleware
The package provides middleware for protecting routes.
Permission Middleware
// Require single permission Route::get('/posts/create', CreatePost::class) ->middleware('permission:post.create'); // Require any of multiple permissions (OR) Route::get('/posts', ViewPosts::class) ->middleware('permission:post.view|post.view_any');
Role Middleware
// Require single role Route::get('/admin', AdminDashboard::class) ->middleware('role:admin'); // Require any of multiple roles (OR) Route::get('/dashboard', Dashboard::class) ->middleware('role:admin|editor');
Role or Permission Middleware
// Require role OR permission Route::get('/posts', ViewPosts::class) ->middleware('role.or.permission:admin|post.view');
Model Actions
The ModelActions enum defines which actions to generate permissions for:
<?php namespace App\Enums; enum ModelActions: string { case ViewAny = 'view_any'; case View = 'view'; case Create = 'create'; case Edit = 'update'; case Delete = 'delete'; case Restore = 'restore'; case ForceDelete = 'force_delete'; }
You can add custom actions:
enum ModelActions: string { // ... existing cases case Publish = 'publish'; case Archive = 'archive'; }
Run php artisan sync:permissions after modifying the enum.
Cascade Deletion
By default, when a user is deleted, their role and permission assignments remain in the database. To automatically clean up these relationships, use the CascadeDeletesRolesAndPermissions trait.
Setup
Add the trait to your User model alongside HasRoles:
use Techieni3\LaravelUserPermissions\Traits\HasRoles; use Techieni3\LaravelUserPermissions\Traits\CascadeDeletesRolesAndPermissions; class User extends Authenticatable { use HasRoles, CascadeDeletesRolesAndPermissions; }
How It Works
When a user is deleted, the trait will:
- Detach all assigned roles
- Detach all direct permissions
- Dispatch a
RolesAndPermissionsDeletedevent (if events are enabled)
The trait automatically detects if your model uses soft deletes:
- Hard deletes: Triggers on
deletingevent - Soft deletes: Triggers on
forceDeletingevent (permanent deletion only)
Important Notes
Bulk Deletes: The trait only works for individual model deletions:
// ✅ Will trigger cascade deletion $user->delete(); $user->forceDelete(); // For soft-deleted models // ❌ Won't trigger cascade deletion (bypasses Eloquent) User::where('status', 'inactive')->delete(); DB::table('users')->where('status', 'inactive')->delete();
For bulk deletes, use this pattern instead:
// Load models first to trigger events User::where('status', 'inactive')->get()->each->delete();
Cleaning Up Orphaned Records
If you have orphaned pivot table records (from bulk deletes, database migrations, or other operations that bypass Eloquent events), use the cleanup command:
# Preview orphaned records without deleting php artisan permissions:cleanup-orphans --dry-run # Remove orphaned records php artisan permissions:cleanup-orphans
This command will:
- Remove
users_rolesrecords where the user or role no longer exists - Remove
users_permissionsrecords where the user or permission no longer exists - Remove
roles_permissionsrecords where the role or permission no longer exists
Scheduling Periodic Cleanup: Add to your app/Console/Kernel.php to run automatically:
protected function schedule(Schedule $schedule) { // Run cleanup daily at 2 AM $schedule->command('permissions:cleanup-orphans')->dailyAt('02:00'); // Or run weekly $schedule->command('permissions:cleanup-orphans')->weekly(); }
Note: This cleanup command is particularly useful if you are using bulk delete operations
Events
The package dispatches events when roles and permissions change, allowing you to react to authorization changes in your application.
Available Events
| Event | Triggered When | Properties |
|---|---|---|
RoleAdded |
A single role is added to a user | $model, $role |
RoleRemoved |
A single role is removed from a user | $model, $role |
RolesSynced |
Roles are synced (bulk operation) | $model, $synced, $previous, $attached, $detached |
PermissionAdded |
A single permission is added to a user | $model, $permission |
PermissionRemoved |
A single permission is removed from a user | $model, $permission |
PermissionsSynced |
Permissions are synced (bulk operation) | $model, $synced, $previous, $attached, $detached |
RolesAndPermissionsDeleted |
User is deleted with cascade trait | $model, $roles, $permissions |
Listening to Events
use Techieni3\LaravelUserPermissions\Events\RoleAdded; use Techieni3\LaravelUserPermissions\Events\RoleRemoved; use Techieni3\LaravelUserPermissions\Events\RolesSynced; use Techieni3\LaravelUserPermissions\Events\PermissionAdded; use Techieni3\LaravelUserPermissions\Events\PermissionRemoved; use Techieni3\LaravelUserPermissions\Events\PermissionsSynced; // Listen to single role assignment Event::listen(RoleAdded::class, function (RoleAdded $event) { $user = $event->model; $role = $event->role; Log::info("Role {$role->name->value} added to user {$user->id}"); }); // Listen to bulk role sync Event::listen(RolesSynced::class, function (RolesSynced $event) { $user = $event->model; Log::info("User {$user->id} roles synced", [ 'added' => $event->attached->pluck('name.value'), 'removed' => $event->detached->pluck('name.value'), 'current' => $event->synced->pluck('name.value'), ]); }); // Listen to permission changes Event::listen(PermissionsSynced::class, function (PermissionsSynced $event) { $user = $event->model; Log::info("User {$user->id} permissions synced", [ 'added' => $event->attached->pluck('name'), 'removed' => $event->detached->pluck('name'), ]); }); // Listen to cascade deletion (requires CascadeDeletesRolesAndPermissions trait) Event::listen(RolesAndPermissionsDeleted::class, function (RolesAndPermissionsDeleted $event) { $user = $event->model; Log::info("User {$user->id} deleted with cascade cleanup", [ 'roles_removed' => $event->roles->pluck('name.value'), 'permissions_removed' => $event->permissions->pluck('name'), ]); });
Event Properties
Individual Events (RoleAdded, RoleRemoved, PermissionAdded, PermissionRemoved):
$model- The user model$roleor$permission- The role/permission that was added or removed
Sync Events (RolesSynced, PermissionsSynced):
$model- The user model$synced- Collection of roles/permissions after sync$previous- Collection of roles/permissions before sync$attached- Collection of roles/permissions that were added$detached- Collection of roles/permissions that were removed
Cascade Deletion Event (RolesAndPermissionsDeleted):
$model- The user model being deleted$roles- Collection of roles that were detached$permissions- Collection of direct permissions that were detached
Disabling Events
Disable events by setting events_enabled to false in config or via environment variable:
PERMISSIONS_EVENTS_ENABLED=false
Model Discovery Configuration
The package provides flexible options for discovering models to generate permissions.
Including Models and Directories
Specify which models to include using the included array. You can mix directory paths and specific model classes:
'models' => [ 'included' => [ // Scan entire directories app_path('Models'), app_path('Modules/Blog/Models'), app_path('Modules/Shop/Models'), // Include specific model classes App\External\CustomModel::class, App\ThirdParty\SpecialModel::class, ], ],
Excluding Models
To prevent specific models from having permissions generated, use the excluded array. This filters out models discovered from directories in the included array:
'models' => [ 'included' => [ app_path('Models'), ], 'excluded' => [ App\Models\User::class, App\Models\PersonalAccessToken::class, ], ],
Caching
The package uses request-level caching for performance. Permissions and roles are cached during a single request and automatically cleared when changes occur.
You don't need to manually clear caches - the package handles this for you.
Testing
Run the test suite:
composer test
Run static analysis:
composer analyse
Format code:
composer format
Similar Packages
If this package doesn't fit your needs, check out these alternatives:
- spatie/laravel-permission - The most popular Laravel permissions package with teams support and blade directives
- JosephSilber/bouncer - Flexible roles and abilities with a focus on simplicity and model-level permissions
Security
If you discover a security issue, please email niteen1593@gmail.com instead of using the issue tracker.
License
The MIT License (MIT). Please see License for more information.


