amdadulhaq/guard-laravel

Guard is Role and Permission management system for Laravel

Maintainers

Package info

github.com/amdad121/guard-laravel

pkg:composer/amdadulhaq/guard-laravel

Fund package maintenance!

amdad121

Statistics

Installs: 5 459

Dependents: 0

Suggesters: 0

Stars: 13

Open Issues: 0

v2.0.5 2026-06-23 09:20 UTC

README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads PHP Version Laravel Version Sponsor

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, @cannot support
  • Middleware Protection - role, permission, and role_or_permission middleware
  • Blade Directives - @role, @hasrole, @hasanyrole, @hasallroles
  • Type-Safe Enums - IDE-friendly PermissionType and CacheKey enums
  • 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

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 definitions
  • permissions - Permission definitions
  • permission_role - Role-permission relationships
  • role_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:

  • Roleable trait 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-role prompts for an optional user identifier and accepts a user ID, email, or name.
  • guard:create-permission prompts 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 relationship
  • assignRole(...$roles) - Assign one or more roles
  • syncRoles(array $roles, bool $detach = true) - Sync roles
  • syncRolesWithoutDetaching(array $roles) - Sync without detaching
  • revokeRole($role) - Revoke specific role
  • revokeRoles() - Revoke all roles
  • getRoleNames() - Get all role names
  • hasRole($role) - Check single role
  • hasAllRoles(...$roles) - Check all roles
  • hasAnyRole(...$roles) - Check any role
  • getPermissionNames() - Get permission names inherited from roles
  • hasPermission($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 name
  • isProtectedRole() - Check if guarded
  • getPermissionNames() - Get assigned permission names
  • permissions() - BelongsToMany to permissions
  • users() - BelongsToMany to users

Scopes:

  • guarded() - Only guarded roles
  • unguarded() - 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 name
  • getLabel() - Get human-readable label
  • getDescription() - Get description
  • isWildcard() - Check if wildcard pattern
  • getGroup() - Get resource group (e.g., 'users')
  • getType() - Get PermissionType enum
  • roles() - BelongsToMany to roles

Scopes:

  • wildcard() - Only wildcard permissions
  • byGroup($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

  1. Keep caching enabled in production

  2. Use wildcard permissions to reduce permission count

  3. 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'));
  4. 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

Contributors

License

The MIT License (MIT). See License File for details.

Made with ❤️ for the Laravel community