Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

Laravel 13 User Roles and Permissions Tutorial with Spatie

๐Ÿ› ๏ธ Laravel Tutorials4 min read

Laravel 13 User Roles and Permissions Tutorial with Spatie

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 6, 2026
Laravel 13 User Roles and Permissions Tutorial with Spatie

The simplest way to add roles and permissions to Laravel 13 is the spatie/laravel-permission package. Install it, publish and run its migrations, add the HasRoles trait to your User model, create roles and permissions in a seeder, then protect routes with the role and permission middleware. This guide walks through each step with code.

Laravel itself ships gates and policies for authorization. A roles-and-permissions package adds the database tables and helper methods that let you assign roles to users and check them anywhere in your app.

What do I need before I start?

  • Laravel 13.x on PHP 8.3 or newer, with authentication already working.
  • A database connection and a users table.
  • Check the package's prerequisites page for the Laravel and PHP versions supported by its current major release before you install.

Step 1: How do I install the package?

Code
composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan optimize:clear
php artisan migrate

The service provider registers itself automatically. The publish command copies the config file and the migration that creates the roles, permissions and pivot tables.

Step 2: How do I add roles to the User model?

PHP
use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;

    // ...
}

Step 3: How do I create roles and permissions?

Use a seeder so the setup is repeatable. Name permissions after the action they allow, for example posts.create. Assign permissions to roles, and roles to users.

JavaScript
namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;

class RolesSeeder extends Seeder
{
    public function run(): void
    {
        app()[PermissionRegistrar::class]->forgetCachedPermissions();

        foreach (['posts.view', 'posts.create', 'posts.delete'] as $name) {
            Permission::findOrCreate($name, 'web');
        }

        Role::findOrCreate('editor', 'web')->syncPermissions(['posts.view', 'posts.create']);
        Role::findOrCreate('admin', 'web')->syncPermissions(Permission::all());

        User::firstWhere('email', 'admin@example.com')?->assignRole('admin');
    }
}

I ran php -l on this seeder to check the syntax. I did not run it against a database for this article. Run it with php artisan db:seed --class=RolesSeeder.

The first line clears the package's permission cache. Without it, a seeder that runs twice can read stale data.

Step 4: How do I protect routes?

In Laravel 11 and later, register the middleware aliases in bootstrap/app.php:

JavaScript
use Illuminate\Foundation\Configuration\Middleware;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->alias([
        'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
        'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
        'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
    ]);
})

Then use them on routes:

CSS
Route::middleware(['auth', 'role:admin'])->group(function () {
    Route::get('/admin', [AdminController::class, 'index']);
});

Route::post('/posts', [PostController::class, 'store'])
    ->middleware(['auth', 'permission:posts.create']);

How do I check roles and permissions in code and views?

Shell
$user->hasRole('admin');
$user->hasAnyRole(['admin', 'editor']);
$user->can('posts.delete');
$user->givePermissionTo('posts.view');
$user->removeRole('editor');

Because the package integrates with Laravel's gate, the built-in @can directive works in Blade, and the package adds @role too:

SQL
@can('posts.delete')
    <button>Delete</button>
@endcan

@role('admin')
    <a href="/admin">Admin area</a>
@endrole

Should I check roles or permissions?

Check permissions in your code and use roles only as named bundles of permissions. A check like can('posts.delete') keeps working if you later add a "moderator" role. A check like hasRole('admin') breaks as soon as another role needs the same access.

How do I verify it works?

  1. Seed the roles and assign editor to a test user.
  2. Log in as that user and open an admin route. You should get a 403 response.
  3. Assign admin and reload. The route should now load.
  4. Write a feature test that acts as each user and asserts the status code, so the rules cannot regress silently.
PHP
it('blocks editors from the admin area', function () {
    $user = User::factory()->create()->assignRole('editor');

    $this->actingAs($user)->get('/admin')->assertForbidden();
});

This test uses Pest syntax and is shown as a pattern. It needs the roles to exist in the test database, so call the seeder in your test setup. I have not executed it here.

What are the common mistakes?

  • Stale cache. Permissions are cached. After changing them by hand in the database, run php artisan permission:cache-reset.
  • Wrong guard. A role created for one guard does not apply to a user authenticated through another guard. Keep the guard name consistent.
  • Forgetting the trait. Without HasRoles, methods like assignRole do not exist.
  • Hiding buttons only. Hiding a button in Blade is not security. Always protect the route too.
  • Skipping migrate. The tables must exist before you seed.

FAQ

Do I need a package for roles in Laravel?

No. Gates and policies cover many cases. A package becomes useful when admins must create roles and assign them at runtime, because that data needs database tables.

Can a user have more than one role?

Yes. A user can hold several roles, and permissions from all of them apply. Use assignRole repeatedly or pass an array.

Can I give a single permission directly to a user?

Yes, with givePermissionTo. Use this sparingly, since role-based assignment is easier to audit.

References

Article Index & Topic Tags
LaravelAuthorizationRolesPermissions

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.