amdadulhaq / guard-laravel
Guard is Role and Permission management system for Laravel
Fund package maintenance!
Requires
- php: ^8.2|^8.3|^8.4|^8.5
- illuminate/contracts: ^10.0|^11.0|^12.0|^13.0
- illuminate/database: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- driftingly/rector-laravel: ^1.0|^2.0
- larastan/larastan: ^2.0|^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- pestphp/pest: ^2.0|^3.0|^4.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0|^4.0
This package is auto-updated.
Last update: 2026-06-23 10:18:41 UTC
README
A powerful, flexible, and developer-friendly role and permission management system for Laravel applications.
Quick Start
Get up and running in 5 minutes:
Upgrading from an older version? Check the Upgrade Guide for detailed migration instructions.
1. Install via Composer
composer require amdadulhaq/guard-laravel
2. Publish and run migrations
php artisan vendor:publish --tag="guard-migrations"
php artisan migrate
3. Setup your User model
<?php namespace App\Models; use AmdadulHaq\Guard\Contracts\Roleable as RoleableContract; use AmdadulHaq\Guard\Concerns\Roleable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable implements RoleableContract { use Roleable; }
4. Create your first role and permission
php artisan guard:create-role admin Administrator
php artisan guard:create-permission users.create "Create Users"
5. Protect your routes
Route::middleware('role:admin')->get('/admin', [AdminController::class, 'index']);
Features
- Modern PHP & Laravel - Built for PHP 8.2+ and Laravel 10/11/12/13
- Flexible Permission System - Users can have permissions via roles
- Wildcard Permissions - Use
posts.*to match all post-related permissions - Smart Caching - Automatic cache invalidation for optimal performance
- Laravel Gate Integration - Native
@can,@canany,@cannotsupport - Middleware Protection -
role,permission, androle_or_permissionmiddleware - Blade Directives -
@role,@hasrole,@hasanyrole,@hasallroles - Type-Safe Enums - IDE-friendly
PermissionTypeandCacheKeyenums - Guarded Roles - Protect critical roles from accidental deletion
- Permission Groups - Organize permissions by resource
- Interactive Commands - Laravel Prompts for creating roles/permissions
- Clean Architecture - Separated concerns with traits and contracts
- Developer Tools - Pint, Pest, Rector, and Larastan included
Support & Sponsorship
Building and maintaining high-quality open-source packages takes hundreds of hours of dedicated time. If you use Guard in your commercial applications or it saves you significant development time, please consider supporting the project.
Sponsor the Project Ensure the package stays actively maintained, receives rapid bug fixes, and continuous feature updates by becoming a monthly sponsor.
Table of Contents
- Installation
- Upgrade Guide
- Configuration
- Usage
- Models Reference
- Exceptions
- Caching
- Database Structure
- Enums
- Development
- Troubleshooting
- FAQ
Installation
Requirements
- PHP: 8.2, 8.3, 8.4, or 8.5
- Laravel: 10.x, 11.x, 12.x, or 13.x
- Database: MySQL 5.7+, PostgreSQL 9.6+, SQLite 3.8+, or SQL Server 2017+
Step 1: Install via Composer
composer require amdadulhaq/guard-laravel
Step 2: Publish Migrations
php artisan vendor:publish --tag="guard-migrations"
php artisan migrate
This creates 4 tables:
roles- Role definitionspermissions- Permission definitionspermission_role- Role-permission relationshipsrole_user- User-role relationships Pivot table names are derived from model table names; the defaults shown above are used unless you customize model tables.
Step 3: Configure User Model
<?php namespace App\Models; use AmdadulHaq\Guard\Contracts\Roleable as RoleableContract; use AmdadulHaq\Guard\Concerns\Roleable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable implements RoleableContract { use Roleable; }
Step 4: (Optional) Publish Config
php artisan vendor:publish --tag="guard-config"
Configuration
The config/guard.php file:
return [ 'models' => [ 'user' => \App\Models\User::class, 'role' => \AmdadulHaq\Guard\Models\Role::class, 'permission' => \AmdadulHaq\Guard\Models\Permission::class, ], 'tables' => [ 'roles' => 'roles', 'permissions' => 'permissions', ], 'cache' => [ 'enabled' => env('GUARD_CACHE_ENABLED', true), 'roles_duration' => (int) env('GUARD_ROLES_CACHE_DURATION', 3600), 'permissions_duration' => (int) env('GUARD_PERMISSIONS_CACHE_DURATION', 3600), ], 'middleware' => [ 'role' => 'role', 'permission' => 'permission', 'role_or_permission' => 'role_or_permission', ], 'wildcard' => [ 'enabled' => env('GUARD_WILDCARD_ENABLED', true), ], ];
Usage
User Setup
use AmdadulHaq\Guard\Contracts\Roleable as RoleableContract; use AmdadulHaq\Guard\Concerns\Roleable; class User extends Authenticatable implements RoleableContract { use Roleable; }
Notes:
Roleabletrait on the user model handles both role and permission checks.- Users do not receive permissions directly.
- Assign permissions to roles, then users inherit them from those roles.
Creating Roles
use AmdadulHaq\Guard\Models\Role; // Create a role $adminRole = Role::create([ 'name' => 'administrator', 'label' => 'Administrator', 'description' => 'Full system access', 'is_guarded' => true, // Protected from deletion ]); // Create via command php artisan guard:create-role moderator "Moderator" php artisan guard:create-role moderator "Moderator" 1 php artisan guard:create-role moderator "Moderator" user@example.com php artisan guard:create-role moderator "Moderator" "Jane Doe"
Role Model Methods:
$role->getName(); // Get role name $role->isProtectedRole(); // Check if guarded $role->getPermissionNames(); // Get all permission names $role->users; // Get users with this role // Query scopes Role::guarded()->get(); // Only guarded roles Role::unguarded()->get(); // Only unguarded roles
Creating Permissions
use AmdadulHaq\Guard\Models\Permission; // Simple permission Permission::create([ 'name' => 'users.create', 'label' => 'Create Users', 'description' => 'Can create new users', 'group' => 'users', // For organization ]); // Wildcard permission (auto-sets is_wildcard = true) Permission::create([ 'name' => 'posts.*', 'label' => 'Manage All Posts', 'group' => 'posts', ]); // Create via command php artisan guard:create-permission users.delete "Delete Users" php artisan guard:create-permission users.delete "Delete Users" 1 php artisan guard:create-permission users.delete "Delete Users" admin
Permission Model Methods:
$permission->getName(); // Get permission name $permission->getLabel(); // Get human-readable label $permission->getDescription(); // Get description $permission->isWildcard(); // Check if wildcard (e.g., posts.*) $permission->getGroup(); // Get group (e.g., 'users' from 'users.create') $permission->getType(); // Get PermissionType enum (e.g., PermissionType::CREATE) $permission->roles; // Get roles with this permission // Query scopes Permission::wildcard()->get(); // Only wildcard permissions Permission::byGroup('users')->get(); // Permissions in users group
Wildcard Permissions
Wildcard permissions automatically match all sub-permissions:
// Create wildcard permission Permission::create(['name' => 'posts.*']); // Assign to role $role->givePermissionTo('posts.*'); // Now user can do all of these: $user->hasPermission('posts.create'); // true $user->hasPermission('posts.update'); // true $user->hasPermission('posts.delete'); // true $user->hasPermission('posts.publish'); // true
The is_wildcard boolean is automatically set when the name ends with *.
Role Management
Assigning Roles:
// Single role $user->assignRole('administrator'); // by role name $user->assignRole($roleModel); // by role model // Multiple roles in one call $user->assignRole('administrator', 'editor'); $user->assignRole([$roleModel, $roleId, 'moderator']); // Sync (replaces all) $user->syncRoles(['administrator', 'editor']); $user->syncRoles([$role1->id, $role2->id]); // Sync without detaching existing $user->syncRolesWithoutDetaching(['moderator']); // Revoke $user->revokeRole('editor'); $user->revokeRole($roleModel); $user->revokeRoles(); // Revoke all
Checking Roles:
// Single role $user->hasRole('administrator'); // true/false // Multiple roles $user->hasAllRoles(['admin', 'editor']); // Must have ALL $user->hasAnyRole(['admin', 'moderator']); // Must have ANY // Get role names $user->getRoleNames(); // ['administrator', 'editor']
Permission Management
Assigning to Roles:
// Single permission $role->givePermissionTo('users.create'); // by permission name $role->givePermissionTo($permissionModel); // by permission model // Multiple permissions in one call $role->givePermissionTo('users.create', 'users.edit'); $role->givePermissionTo([$permissionModel, $permissionId, 'users.delete']); // Sync (replaces all) $role->syncPermissions(['users.create', 'users.edit']); $role->syncPermissions([$perm1->id, $perm2->id]); // Revoke $role->revokePermissionTo('users.delete'); $role->revokePermissionTo($permissionModel); $role->revokeAllPermissions();
Checking Role Permissions:
$role->hasPermissionTo('users.edit'); // Check if role has permission $role->getPermissionNames(); // Get all permission names
Checking User Permissions:
// Check by name $user->hasPermission('users.create'); // Check by model $user->hasPermission($permissionModel); // Wildcard matching $user->hasPermission('posts.*'); // Get all permissions inherited from roles $user->getPermissions(); // Get permission names array $user->getPermissionNames(); // ['users.create', 'users.edit']
Checking Access
Role Checking:
if ($user->hasRole('administrator')) { // User has administrator role } if ($user->hasAllRoles(['admin', 'editor'])) { // User has both roles } if ($user->hasAnyRole(['admin', 'moderator'])) { // User has at least one role } // Get all role names $user->getRoleNames(); // ['administrator', 'editor']
Permission Checking:
if ($user->hasPermission('users.create')) { // User can create users } if ($user->hasPermission('posts.*')) { // User has wildcard permission for posts }
Middleware
All middleware supports multiple values (requires ANY):
// Role middleware Route::middleware('role:administrator')->get('/admin', [AdminController::class, 'index']); // Multiple roles (requires ANY) Route::middleware('role:admin,editor')->group(function () { Route::get('/dashboard', [DashboardController::class, 'index']); }); // Permission middleware Route::middleware('permission:users.create')->post('/users', [UserController::class, 'store']); // Multiple permissions (requires ANY) Route::middleware('permission:users.create,users.edit')->put('/users/{id}', [UserController::class, 'update']); // Role OR permission middleware Route::middleware('role_or_permission:admin,users.create')->get('/users', [UserController::class, 'index']); // Multiple role_or_permission Route::middleware('role_or_permission:admin,editor,posts.manage')->group(function () { Route::post('/manage', [Controller::class, 'handle']); });
Gate Integration
The package automatically registers Gates for all permissions and roles:
// In controllers public function store(Request $request) { $this->authorize('users.create'); // User can create users } // Using Gate facade use Illuminate\Support\Facades\Gate; if (Gate::allows('users.create')) { // Allowed } if (Gate::denies('users.delete')) { abort(403, 'Permission denied'); } // Check for specific user if (Gate::forUser($otherUser)->allows('posts.edit')) { // That user can edit posts } // Authorize roles $this->authorize('administrator');
Blade Directives
Guard provides custom Blade directives for role checking, in addition to Laravel's built-in @can directives:
Custom Role Directives:
@role('administrator') <div class="admin-panel"> <h1>Admin Dashboard</h1> </div> @endrole @hasrole('editor') <p>Editor content here</p> @endhasrole @hasanyrole(['administrator', 'moderator']) <p>Content for admins or moderators</p> @endhasanyrole @hasallroles(['administrator', 'editor']) <p>Only for users with BOTH admin AND editor roles</p> @endhasallroles
Built-in Laravel Directives (via Gate integration):
@can('users.create') <a href="/users/create">Create User</a> @endcan @canany(['users.create', 'users.edit']) <p>You can manage users</p> @endcanany @cannot('users.delete') <p>You cannot delete users</p> @endcannot
Artisan Commands
Create a Role:
php artisan guard:create-role admin Administrator
# With optional user assignment as the third positional argument
php artisan guard:create-role moderator Moderator 1
Create a Permission:
php artisan guard:create-permission users.create "Create Users" # With optional role assignment as the third positional argument php artisan guard:create-permission posts.delete "Delete Posts" 1
Both commands support Laravel Prompts when optional assignment arguments are omitted.
guard:create-roleprompts for an optional user identifier and accepts a user ID, email, or name.guard:create-permissionprompts for an optional role identifier and accepts a role ID or role name.
Query Scopes
// Users with a specific role User::query()->withRoles('administrator')->get(); // Users with a specific permission inherited through roles User::query()->withPermissions('users.create')->get(); // Role scopes Role::query()->guarded()->get(); Role::query()->unguarded()->get(); // Permission scopes Permission::query()->wildcard()->get(); Permission::query()->byGroup('users')->get();
Models Reference
User Model (via Traits)
Roleable trait provides:
roles()- BelongsToMany relationshipassignRole(...$roles)- Assign one or more rolessyncRoles(array $roles, bool $detach = true)- Sync rolessyncRolesWithoutDetaching(array $roles)- Sync without detachingrevokeRole($role)- Revoke specific rolerevokeRoles()- Revoke all rolesgetRoleNames()- Get all role nameshasRole($role)- Check single rolehasAllRoles(...$roles)- Check all roleshasAnyRole(...$roles)- Check any rolegetPermissionNames()- Get permission names inherited from roleshasPermission($permission)- Check permission (by name or model)getPermissions()- Get all permissions inherited from roles
Role Model
Properties:
name(string, unique)label(string, nullable)description(text, nullable)is_guarded(boolean)
Methods:
getName()- Get role nameisProtectedRole()- Check if guardedgetPermissionNames()- Get assigned permission namespermissions()- BelongsToMany to permissionsusers()- BelongsToMany to users
Scopes:
guarded()- Only guarded rolesunguarded()- Only unguarded roles
Permission Model
Properties:
name(string, unique)label(string, nullable)description(text, nullable)group(string, nullable, indexed)is_wildcard(boolean, auto-set)
Methods:
getName()- Get permission namegetLabel()- Get human-readable labelgetDescription()- Get descriptionisWildcard()- Check if wildcard patterngetGroup()- Get resource group (e.g., 'users')getType()- Get PermissionType enumroles()- BelongsToMany to roles
Scopes:
wildcard()- Only wildcard permissionsbyGroup($group)- Filter by group
Exceptions
use AmdadulHaq\Guard\Exceptions\PermissionDeniedException; // Thrown when user lacks required permission/role throw PermissionDeniedException::create('users.delete'); throw PermissionDeniedException::roleNotAssigned('administrator');
Caching
The package uses intelligent caching:
use AmdadulHaq\Guard\Facades\Guard; // Clear cache manually Guard::clearCache();
Cache is automatically cleared when:
- Roles or permissions are created/updated/deleted
- Role-permission relationships change
Configuration:
'cache' => [ 'enabled' => true, 'roles_duration' => 3600, // 1 hour 'permissions_duration' => 3600, // 1 hour ],
Database Structure
Roles Table
Schema::create('roles', function (Blueprint $table) { $table->id(); $table->string('name')->unique(); $table->string('label')->nullable(); $table->text('description')->nullable(); $table->boolean('is_guarded')->default(false); $table->timestamps(); });
Permissions Table
Schema::create('permissions', function (Blueprint $table) { $table->id(); $table->string('name')->unique(); $table->string('label')->nullable(); $table->text('description')->nullable(); $table->string('group')->nullable()->index(); $table->boolean('is_wildcard')->default(false); $table->timestamps(); });
Permission-Role Pivot
Schema::create('permission_role', function (Blueprint $table) { $table->foreignId('permission_id')->constrained()->cascadeOnDelete(); $table->foreignId('role_id')->constrained()->cascadeOnDelete(); $table->primary(['permission_id', 'role_id']); });
Role-User Pivot
Schema::create('role_user', function (Blueprint $table) { $table->foreignId('role_id')->constrained()->cascadeOnDelete(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->primary(['role_id', 'user_id']); });
Enums
PermissionType
use AmdadulHaq\Guard\Enums\PermissionType; PermissionType::CREATE->label(); // "Create" PermissionType::READ->label(); // "Read" PermissionType::WRITE->label(); // "Write" PermissionType::UPDATE->label(); // "Update" PermissionType::DELETE->label(); // "Delete" PermissionType::VIEW_ANY->label(); // "View any" PermissionType::VIEW->label(); // "View" PermissionType::RESTORE->label(); // "Restore" PermissionType::FORCE_DELETE->label(); // "Force delete" PermissionType::MANAGE->label(); // "Manage"
CacheKey
use AmdadulHaq\Guard\Enums\CacheKey; CacheKey::PERMISSIONS->value; // 'guard_permissions' CacheKey::ROLES->value; // 'guard_roles'
Development
Code Quality Tools
# Rector (code refactoring) composer refactor composer refactor:check # Laravel Pint (code style) composer lint composer lint:check # Pest (testing) composer test composer test-coverage # Larastan (static analysis) composer analyse
Running Tests
# Run all tests composer test # With coverage composer test-coverage
Troubleshooting
Common Issues
Issue: Class 'AmdadulHaq\Guard\Concerns\Roleable' not found
Solution:
composer dump-autoload
Issue: Target class [role] does not exist.
Solution:
php artisan config:clear
Issue: Permissions not being recognized
Solution:
php artisan cache:clear # Or php artisan tinker --execute="\AmdadulHaq\Guard\Facades\Guard::clearCache()"
Performance Tips
-
Keep caching enabled in production
-
Use wildcard permissions to reduce permission count
-
Filter at database level instead of loading all users:
// Good User::whereHas('roles', fn ($q) => $q->where('name', 'admin'))->get(); // Less efficient User::all()->filter(fn ($u) => $u->hasRole('admin'));
-
Eager load when needed:
User::with(['roles', 'roles.permissions'])->get();
FAQ
Q: Can I use this with Laravel Sanctum?
A: Yes! Guard works seamlessly with Sanctum and any auth system.
Q: Can users have permissions without roles?
A: No, users receive permissions via roles.
Q: How do wildcard permissions work?
A: Create a permission like posts.* and it automatically matches posts.create, posts.edit, etc.
Q: Can I customize table names?
A: Yes, publish the config and modify the tables section.
Q: Does it work with multiple guards?
A: Yes, it integrates with Laravel's authorization system.
Q: Is there a UI for managing roles?
A: Guard is backend-only. For a UI, consider Filament Shield or build your own.
Q: What Blade directives does Guard provide?
A: Guard ships with @role, @hasrole, @hasanyrole, and @hasallroles. Laravel's built-in @can, @canany, and @cannot also work through Gate integration.
Q: Can permissions be assigned to permissions?
A: No, permissions are assigned to roles.
Contributing
We welcome contributions! Please see CONTRIBUTING for details.
Changelog
See CHANGELOG for recent changes.
Security
Please review our security policy for reporting vulnerabilities.
Credits
License
The MIT License (MIT). See License File for details.
Made with ❤️ for the Laravel community