Refactoring Laravel SAAS: Granular Access with Spatie Permission
Overview
Hardcoding an is_admin boolean into your users table works for simple projects, but it falls apart the moment a client asks for a "Viewer" or "Manager" role. In a SAAS environment, granularity is the goal. By integrating the Spatie Laravel Permission package, you transform a rigid binary system into a dynamic database-driven engine. This technique allows you to define specific permissions—like task_create or project_view—and group them into roles that can be assigned and modified without touching a single line of application code.
Prerequisites
To follow this refactor, you should be comfortable with Laravel fundamentals, specifically Eloquent models, migrations, and Form Requests. Familiarity with PHP Enums in PHP is beneficial, as they remain the best way to maintain a "source of truth" for role names even when data is stored in the database.
Key Libraries & Tools
- Spatie Laravel Permission: The industry-standard package for managing roles and permissions in the database.
- Laravel Fortify: Used here for handling user registration hooks.
- Livewire: Utilized for dynamic interface elements like invitation forms.

Code Walkthrough
1. Installation and Model Setup
First, pull in the package and apply the HasRoles trait to your User model. This trait provides the necessary relationship methods to link users to their roles and permissions.
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasRoles;
// ...
}
2. Implementation in Policies
Instead of checking roles directly in your controllers, wrap the logic in Laravel Policies. This keeps your authorization logic centralized. Combine the package's hasPermissionTo method with your multi-tenancy ownership checks.
public function update(User $user, Task $task)
{
return $user->hasPermissionTo('task_edit')
&& $user->team_id === $task->team_id;
}
3. Database Seeding
Define your roles and permissions in a dedicated seeder. This ensures your environment is consistent across development and production.
$admin = Role::create(['name' => RoleEnum::ADMIN->value]);
$admin->givePermissionTo(Permission::all());
$viewer = Role::create(['name' => RoleEnum::VIEWER->value]);
$viewer->givePermissionTo('task_view');
Syntax Notes
Notice the shift from $user->is_admin to $user->can('task_create'). Laravel’s built-in @can directive in Blade automatically hooks into the Spatie package, allowing you to hide menu items or buttons based on the underlying database permissions seamlessly.
Tips & Gotchas
Always assign a default role during user creation. If you use Laravel Factories for testing, leverage the afterCreating hook to ensure every test user has a role, or your authorization tests will fail by default.
- Spatie Laravel Permission
- 33%· products
- David Smith
- 17%· people
- Laravel
- 17%· products
- Laravel Factories
- 17%· products
- PHP Enums
- 17%· products

Building Laravel Saas: Part 5/5 - Roles/Permissions
WatchLaravel Daily // 8:39