← Back to skills
extension
Category: Development & EngineeringAPI key requirement unconfirmed

laravel-permission

Spatie Laravel Permission - roles, permissions, middleware, Blade directives, teams, wildcards, super-admin, API, testing. Use when implementing RBAC, role-based access control, or user authorization.

personAuthor: jakexiaohubgithub
<objective> Covers Spatie Laravel Permission for role-based access control: roles and permissions, route middleware, Blade authorization directives, multi-tenant team scoping, wildcard/hierarchical permissions, super-admin bypass, cache management, direct-vs-role permission inheritance, query scopes, event-based audit logging, Policy integration, Sanctum/Passport API usage, custom models (UUID support), and testing patterns. </objective>

Laravel Permission (Spatie)

Agent Workflow (MANDATORY)

Before ANY implementation, spawn 3 agents in parallel, one Agent call each with a name:

  1. fuse-ai-pilot:explore-codebase - Check existing auth patterns
  2. fuse-ai-pilot:research-expert - Verify Spatie Permission docs via Context7
  3. mcp__context7__query-docs - Check Laravel authorization patterns

After implementation, run fuse-ai-pilot:sniper for validation.


Overview

Spatie Laravel Permission provides complete role-based access control (RBAC) for Laravel applications.

| Component | Purpose | |-----------|---------| | Role | Group of permissions (admin, writer) | | Permission | Single ability (edit articles) | | Middleware | Route protection | | Blade Directives | UI authorization | | Teams | Multi-tenant scoping | | Wildcards | Hierarchical permissions | | Super Admin | Bypass all checks | | Events | Audit logging (v6.15.0+) | | Query Scopes | Filter users by role/permission | | API Support | Sanctum/Passport integration | | Policies | Resource-based authorization |


Critical Rules

  1. Seed roles/permissions in DatabaseSeeder
  2. Cache reset after changes: php artisan permission:cache-reset
  3. Use kebab-case for naming: edit-articles
  4. Never hardcode role checks in controllers - use middleware
  5. Set team context early in request for multi-tenant apps
  6. Specify guard for API - permission:edit,api
  7. Clear cache in tests - Reset in setUp()/beforeEach()

Reference Guide

Core Concepts

| Topic | Reference | When to consult | |-------|-----------|-----------------| | Setup | spatie-permission.md | Installation, model setup, core methods | | Middleware | middleware.md | Route protection patterns | | Blade | blade-directives.md | UI authorization directives | | Direct vs Role | direct-permissions.md | Permission inheritance |

Advanced Features

| Topic | Reference | When to consult | |-------|-----------|-----------------| | Teams | teams.md | Multi-tenant permissions | | Wildcards | wildcard-permissions.md | Hierarchical patterns | | Super Admin | super-admin.md | Bypass all permissions | | Custom Models | custom-models.md | UUID, extending models |

Integration

| Topic | Reference | When to consult | |-------|-----------|-----------------| | API Usage | api-usage.md | Sanctum, guards, JSON responses | | Policies | policies.md | Laravel Policy integration | | Query Scopes | query-scopes.md | User::role(), User::permission() | | Events | events.md | Audit logging, notifications |

Operations & Quality

| Topic | Reference | When to consult | |-------|-----------|-----------------| | Cache | cache.md | Performance, debugging | | CLI | artisan-commands.md | Artisan commands | | Testing | testing.md | Tests, factories, setup | | Performance | performance.md | Optimization, N+1, caching |


Templates (Code Examples)

Setup & Seeding

| Template | Purpose | |----------|---------| | UserModel.php.md | User model with HasRoles trait | | RoleSeeder.php.md | Basic role seeding | | PermissionSeeder.php.md | Permission creation seeder | | WildcardSeeder.php.md | Hierarchical permissions |

Routes & Middleware

| Template | Purpose | |----------|---------| | routes-example.md | Protected routes examples | | ControllerMiddleware.php.md | Middleware in controllers | | BladeExamples.blade.md | Blade directive examples |

Teams & Multi-Tenant

| Template | Purpose | |----------|---------| | TeamMiddleware.php.md | Multi-tenant middleware | | TeamSeeder.php.md | Team-scoped roles seeder | | TeamModel.php.md | Team model with boot |

Super Admin & Cache

| Template | Purpose | |----------|---------| | SuperAdminSetup.php.md | Gate::before bypass | | CacheConfig.php.md | Cache configuration | | DeployScript.sh.md | CI/CD cache management |

API Integration

| Template | Purpose | |----------|---------| | ApiPermissionSetup.php.md | API guard + Sanctum | | ApiExceptionHandler.php.md | JSON error responses | | ApiUserResource.php.md | User resource with permissions |

Policies & Events

| Template | Purpose | |----------|---------| | PostPolicy.php.md | Policy with Spatie integration | | PermissionEventListener.php.md | Audit event listeners | | UserQueryExamples.php.md | Query scope examples | | PermissionAudit.php.md | Audit service |

Testing

| Template | Purpose | |----------|---------| | PermissionTest.php.md | Pest & PHPUnit tests | | UserFactory.php.md | Factory with permission states |

Custom Models

| Template | Purpose | |----------|---------| | CustomRole.php.md | Extended Role model | | CustomPermission.php.md | Extended Permission model | | UUIDMigration.php.md | UUID tables migration | | SetupPermissions.php.md | Custom artisan command |


Quick Reference

Assign Role

$user->assignRole('admin');

Check Permission

$user->can('edit articles');

Middleware (Web)

Route::middleware(['role:admin'])->group(fn () => ...);

Middleware (API)

Route::middleware(['auth:sanctum', 'permission:edit,api'])->group(fn () => ...);

Blade

@role('admin') ... @endrole
@can('edit articles') ... @endcan

Query Scopes

User::role('admin')->get();
User::permission('edit articles')->get();

Teams

setPermissionsTeamId($team->id);

Wildcards

$role->givePermissionTo('articles.*');

Super Admin

Gate::before(fn ($user, $ability) =>
    $user->hasRole('Super-Admin') ? true : null
);

Testing

beforeEach(fn () => app(PermissionRegistrar::class)->forgetCachedPermissions());

Feature Matrix

| Feature | Status | Reference | |---------|--------|-----------| | Basic RBAC | ✅ | spatie-permission.md | | Middleware | ✅ | middleware.md | | Blade Directives | ✅ | blade-directives.md | | Multi-Guard (web/api) | ✅ | middleware.md, api-usage.md | | Teams (Multi-Tenant) | ✅ | teams.md | | Wildcard Permissions | ✅ | wildcard-permissions.md | | Super Admin | ✅ | super-admin.md | | Cache Management | ✅ | cache.md | | Direct vs Role Perms | ✅ | direct-permissions.md | | Artisan Commands | ✅ | artisan-commands.md | | UUID Support | ✅ | custom-models.md | | Custom Models | ✅ | custom-models.md | | Events (v6.15.0+) | ✅ | events.md | | Query Scopes | ✅ | query-scopes.md | | Policy Integration | ✅ | policies.md | | API / Sanctum | ✅ | api-usage.md | | Testing | ✅ | testing.md | | Performance | ✅ | performance.md |


Laravel 13 Notes

Spatie Permission 8.x (current 8.3, requires PHP 8.3+ and Laravel 12/13) is Laravel 13 compatible. L13 integrations:

  • Upgrade v6 → v7 → v8: event classes suffixed Event (RoleAttachedEvent…), commands suffixed Command, clearClassPermissions() removed, findByName() / findOrCreate() accept BackedEnum|string (v8) — see events.md
  • Cache: the permission.cache.key key stays explicit (spatie.permission.cache) in config/permission.php → unaffected by L13's switch to hyphenated prefixes
  • Controller attributes: combine #[Authorize] (policy) and #[Middleware('role:admin')] (RBAC), namespace Illuminate\Routing\Attributes\Controllers — see [[laravel-blade]]
  • PHP 8.3: final readonly class for custom Role/Permission DTOs
#[Middleware(['auth', 'role:admin|editor'])]
#[Authorize('update', 'post')]
public function update(UpdatePostRequest $request, Post $post) { /* ... */ }

Best Practices

DO

  • Define a single source of truth: Policy OR Permission (never both for the same action)
  • Use permission:create-post (verb-noun) for clarity
  • Enable teams only for genuine multi-tenancy
  • Cache permissions per user (Cache::remember('user.permissions.'.$id, ...))
  • Super admin via Gate::before() (never via a * wildcard permission)

DON'T

  • Store permissions in the session (use the Spatie cache)
  • Mix hasRole() and hasPermissionTo() without a consistent architecture
  • Hardcode role names in code (use a RoleEnum enum)
  • Forget to flush the cache after assignRole() in a seeder
  • Grant * to a super admin (prefer a targeted Gate::before())