A powerful, flexible, and developer-friendly role and permission management system for Laravel applications.
Get up and running in 5 minutes:
Upgrading from an older version? Check the Upgrade Guide for detailed migration instructions.
composer require amdadulhaq/guard-laravelphp artisan vendor:publish --tag="guard-migrations"
php artisan migrateChoose one setup:
Roles only
<?php
namespace App\Models;
use AmdadulHaq\Guard\Contracts\Roles as RolesContract;
use AmdadulHaq\Guard\Concerns\HasRoles;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements RolesContract
{
use HasRoles;
}Roles + Permissions
<?php
namespace App\Models;
use AmdadulHaq\Guard\Contracts\User as UserContract;
use AmdadulHaq\Guard\Concerns\HasRoles;
use AmdadulHaq\Guard\Concerns\HasPermissions;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements UserContract
{
use HasRoles;
use HasPermissions;
}php artisan guard:create-role admin Administrator
php artisan guard:create-permission users.create "Create Users"Route::middleware('role:admin')->get('/admin', [AdminController::class, 'index']);- π― 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
- Installation
- Upgrade Guide
- Configuration
- Usage
- Models Reference
- Exceptions
- Caching
- Database Structure
- Enums
- Development
- Troubleshooting
- FAQ
- 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+
composer require amdadulhaq/guard-laravelphp artisan vendor:publish --tag="guard-migrations"
php artisan migrateThis 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.
You can use the package in two ways.
Roles only
<?php
namespace App\Models;
use AmdadulHaq\Guard\Contracts\Roles as RolesContract;
use AmdadulHaq\Guard\Concerns\HasRoles;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements RolesContract
{
use HasRoles;
}Roles + Permissions
<?php
namespace App\Models;
use AmdadulHaq\Guard\Contracts\User as UserContract;
use AmdadulHaq\Guard\Concerns\HasRoles;
use AmdadulHaq\Guard\Concerns\HasPermissions;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements UserContract
{
use HasRoles;
use HasPermissions;
}php artisan vendor:publish --tag="guard-config"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),
],
];Use one of these setups depending on what your app needs.
Roles only
use AmdadulHaq\Guard\Contracts\Roles as RolesContract;
use AmdadulHaq\Guard\Concerns\HasRoles;
class User extends Authenticatable implements RolesContract
{
use HasRoles;
}Roles + Permissions
use AmdadulHaq\Guard\Contracts\User as UserContract;
use AmdadulHaq\Guard\Concerns\HasRoles;
use AmdadulHaq\Guard\Concerns\HasPermissions;
class User extends Authenticatable implements UserContract
{
use HasRoles;
use HasPermissions;
}Notes:
HasPermissionson the user model is for permission checks.- Users do not receive permissions directly.
- Assign permissions to roles, then users inherit them from those 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 rolesuse 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" adminPermission 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 groupWildcard 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'); // trueThe is_wildcard boolean is automatically set when the name ends with *.
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 allChecking 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']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 namesChecking 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']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
}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']);
});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');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>
@endhasallrolesBuilt-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>
@endcannotCreate 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 1Create 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" 1Both 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.
// 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();HasRoles 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 role
HasPermissions trait provides:
getPermissionNames()- Get permission names inherited from roleshasPermission($permission)- Check permission (by name or model)getPermissions()- Get all permissions inherited from roles
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
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 rolesgiveRoleTo(...$roles)- Give one or more roles to permissionsyncRoles(array $roles)- Sync rolesrevokeRole($role)- Revoke roleassignRole(...$roles)- Alias for giveRoleTo
Scopes:
wildcard()- Only wildcard permissionsbyGroup($group)- Filter by group
use AmdadulHaq\Guard\Exceptions\PermissionDeniedException;
use AmdadulHaq\Guard\Exceptions\RoleDoesNotExistException;
use AmdadulHaq\Guard\Exceptions\PermissionDoesNotExistException;
// Permission denied
throw PermissionDeniedException::create('users.delete');
throw PermissionDeniedException::roleNotAssigned('administrator');
// Role not found
throw RoleDoesNotExistException::named('admin');
throw RoleDoesNotExistException::withId(123);
// Permission not found
throw PermissionDoesNotExistException::named('users.delete');
throw PermissionDoesNotExistException::withId(456);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
],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();
});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();
});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']);
});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']);
});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"use AmdadulHaq\Guard\Enums\CacheKey;
CacheKey::PERMISSIONS->value; // 'guard_permissions'
CacheKey::ROLES->value; // 'guard_roles'# 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# Run all tests
composer test
# With coverage
composer test-coverageIssue: Class 'AmdadulHaq\Guard\Concerns\HasRoles' not found
Solution:
composer dump-autoloadIssue: Target class [role] does not exist.
Solution:
php artisan config:clearIssue: Permissions not being recognized
Solution:
php artisan cache:clear
# Or
php artisan tinker --execute="\AmdadulHaq\Guard\Facades\Guard::clearCache()"-
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();
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.
We welcome contributions! Please see CONTRIBUTING for details.
See CHANGELOG for recent changes.
Please review our security policy for reporting vulnerabilities.
The MIT License (MIT). See License File for details.
Made with β€οΈ for the Laravel community