spatie/laravel-permission
Database-backed roles and permissions for Laravel. Assign roles and permissions to users, sync them to the Gate, and check abilities with Laravel’s built-in can()/authorize features. Includes migrations, caching, teams, and flexible model setup.
Installation
composer require spatie/laravel-permission
Run migrations:
php artisan migrate
Publish Config (Optional)
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
Modify config/permission.php if needed (e.g., disable teams, adjust model names).
Model Setup
Use the provided traits in your User model (or any model needing permissions):
use Spatie\Permission\Traits\HasRoles;
use Spatie\Permission\Traits\HasPermissions;
class User extends Authenticatable
{
use HasRoles, HasPermissions;
}
First Use Case: Assigning Permissions
// Direct permission assignment
$user->givePermissionTo('edit_articles');
// Role-based assignment
$role = Role::create(['name' => 'editor']);
$role->givePermissionTo('edit_articles');
$user->assignRole($role);
Check Permissions
if ($user->can('edit_articles')) {
// Grant access
}
Permission Management
Permission::create(['name' => 'delete_posts']);
$user->givePermissionTo(['edit_articles', 'publish_posts']);
$user->revokePermissionTo('edit_articles');
Role-Based Access
$adminRole = Role::create(['name' => 'admin']);
$editorRole = Role::create(['name' => 'editor']);
$adminRole->givePermissionTo($editorRole->allPermissions); // Inherit permissions
$user->assignRole('editor');
$user->removeRole('editor');
Policy Integration
class PostPolicy {
public function update(User $user, Post $post) {
return $user->can('edit_articles');
}
}
Middleware for Routes
role or permission middleware:
Route::get('/dashboard', function () {
// ...
})->middleware(['role:admin']);
public function handle(Request $request, Closure $next) {
if (!$request->user()->can('manage_users')) {
abort(403);
}
return $next($request);
}
Teams (Optional)
config/permission.php:
'teams' => [
'enabled' => true,
],
$team = Team::create(['name' => 'Content Team']);
$team->assignRole('editor');
Wildcard Permissions
manage_*):
$user->givePermissionTo('manage_*');
$user->can('manage_posts'); // Returns true
Seeding Permissions: Create a seeder to populate initial roles/permissions:
public function run() {
$admin = Role::create(['name' => 'admin']);
$admin->givePermissionTo(Permission::all());
$user = User::find(1);
$user->assignRole($admin);
}
Caching: Cache permissions for performance (enabled by default):
// Clear cache manually if needed
$user->syncPermissions([]);
API Responses: Return user roles/permissions in API responses:
return response()->json([
'roles' => $user->roles->pluck('name'),
'permissions' => $user->permissions->pluck('name'),
]);
Event Listeners: Listen for permission changes:
public function handle(PermissionAttachedEvent $event) {
// Log or notify when permissions are added
}
Case Sensitivity: Permissions and roles are case-sensitive. Ensure consistency when assigning/checking:
// Fails if permission is 'edit_articles' but checked as 'Edit_Articles'
$user->can('edit_articles'); // Correct
Model Binding:
Ensure your User model (or other models) uses the correct traits (HasRoles, HasPermissions). Forgetting this will cause method errors.
Wildcard Conflicts:
Wildcard permissions (e.g., manage_*) can override explicit permissions. Test thoroughly:
$user->givePermissionTo('manage_posts');
$user->givePermissionTo('manage_*'); // Overrides explicit 'manage_posts'
Teams Feature:
config/permission.php removes all team-related methods (e.g., teams()). Ensure compatibility if using third-party packages that rely on teams.Migration Conflicts:
If you customize the migration tables (e.g., model_morph_key), ensure the package’s migrations match your schema. Override migrations if needed:
// In a custom migration
Schema::create('model_has_permissions', function (Blueprint $table) {
$table->unsignedBigInteger('permission_id');
$table->unsignedBigInteger('model_id');
$table->string('model_type');
$table->primary(['permission_id', 'model_id', 'model_type']);
});
Permission Caching: Caching can cause stale data if permissions are updated dynamically. Clear cache after bulk updates:
$user->syncPermissions([]); // Clears cache
Laravel Gates vs. Permissions: Gates and permissions are separate but can be combined. Avoid redundancy:
// Bad: Duplicate logic
Gate::define('edit_post', function (User $user, Post $post) {
return $user->can('edit_posts'); // Redundant if using middleware
});
Check Database:
Verify permissions/roles exist in roles, permissions, and pivot tables:
SELECT * FROM model_has_roles WHERE model_type = 'App\Models\User';
Enable Query Logging: Debug permission checks with Laravel’s query log:
DB::enableQueryLog();
$user->can('edit_articles');
dd(DB::getQueryLog());
Middleware Order:
Ensure permission middleware runs after auth middleware in app/Http/Kernel.php:
protected $middleware = [
// ...
\App\Http\Middleware\Authenticate::class,
\Spatie\Permission\Middlewares\RoleOrPermission::class,
];
Events for Debugging: Listen for events to trace permission changes:
PermissionAttached::dispatch($user, $permissions);
Custom Permission Models:
Extend the Permission model for additional fields:
class CustomPermission extends Permission {
protected $casts = [
'is_active' => 'boolean',
];
}
Update config/permission.php:
'models' => [
'permission' => CustomPermission::class,
],
Custom Role Hierarchy: Implement role inheritance logic in a service:
class RoleService {
public function getEffectivePermissions(User $user) {
$permissions = $user->permissions;
foreach ($user->roles as $role) {
$permissions = $permissions->merge($role->permissions);
}
return $permissions;
}
}
Dynamic Permissions: Load permissions from external sources (e.g., API) and sync them:
public function syncExternalPermissions(User $user) {
$externalPermissions = $this->fetchFromApi($user);
$user->syncPermissions($externalPermissions);
}
Policy-Based Permissions: Combine policies with permissions for granular control:
class PostPolicy {
public function delete(User $user, Post $post) {
return $user->can('delete_posts') && $post->owner->is($user);
}
}
Custom Guards: Extend the package’s guard logic for non-standard authentication:
class CustomGuard extends Guard {
public function user() {
// Custom user resolution
}
}
Bind the guard in AuthServiceProvider:
$this
How can I help you explore Laravel packages today?