Role And Permission In Laravel 10 using Spatie: The Definitive Guide (2023)

Chimeremze Prevail Ejimadu
16 min readMar 19, 2023

--

Photo by Danielle Rice on Unsplash

Roles and permissions can make or break your web application. If you’re not careful, your users might end up running amok, wreaking havoc on your carefully crafted code. Roles and permissions are like the bouncers at a club. They make sure only the right people get in and keep the troublemakers out. Without them, chaos would ensue, and you’d have a bunch of unruly patrons messing things up. In the same way, managing user roles and permissions in Laravel is essential to keep your application secure and prevent unauthorized access. Luckily, Laravel has got your back, and with the help of the Spatie package, you can easily manage user roles and permissions like a pro. So, get ready to take control of your application and keep those pesky users in line with this definitive guide on roles and permissions in Laravel 10 using Spatie!

Here’s a scenario, we want to display a website to display products for sale, users with the right permission can be able to add, edit and delete the products. We also want to have a super Admin user that can create and give other users permission for various actions. So let’s get building.

A permission is the right to have access to something, such as a page in a web app. A role is just a collection of permissions.

Steps to follow:

Step 1: Installing Laravel 10 Application
Step 2: Create Authentication Using Laravel Breeze
Step 3: Install spatie/laravel-permission Packages
Step 4: Add Middleware
Step 5: Create Models & Migrations
Step 6: Create Database Seeder & Run Migration
Step 7: Create Routes
Step 8: Add Controllers
Step 9: Create Blade File

Step 1: Installing Laravel 10 Application

Run this command in your terminal to create a new laravel 10 project

composer create-project --prefer-dist laravel/laravel permission_tutorial

Step 2: Create Authentication Using Laravel Breeze

Now, we are going to use Laravel Breeze to generate a nice auth scaffolding for our application. Let’s quickly do this. Run the command

composer require laravel/breeze --dev

Once the installation is done, you have to run the artisan command it to install into your application. Follow the prompts as shown below, we’ll be using blade for this tutorial, but you can use the react or vue scaffold.

php artisan breeze:install
Installing laravel breeze

Now, run this command when its done

npm install

Step 3: Install spatie/laravel-permission Packages

Here we install the spatie permissions package with the command below

composer require spatie/laravel-permission

Now, publish this package as below.

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"

Now you can see the permission.php file and migration. So, we need to run migration using the following command. Great!

Note: If you’re caught up or something, All the code can be found in the public repository.

Step 4: Add Middleware

This is a pretty easy step, spatie package provides its in-built middleware, we just need to add this in the Kernel.php file at app\Http\Kernel.php


protected $middlewareAliases = [
....
'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class,
]

Step 5: Create Models & Migrations

Now, let’s look at what we want to create. We already have a User Model in our application, so we will just create a product model and its migration.

php artisan make:model Product -m

app/Models/Product.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
use HasFactory;

protected $fillable = [
'name', 'detail'
];
}

Let’s go to our app/Models/User.php

We have to add the HasRoles trait.

use Spatie\Permission\Traits\HasRoles;


class User extends Authenticatable
{
use HasFactory, Notifiable, HasRoles;

......

Good! Let’s head over to the migrations

// in the create_products_table

.....
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('detail');
$table->timestamps();
});
.....

Step 6: Create Database Seeder & Run Migration

You can create a seeder separately for this, but I used the default for this. In the database/seeders/DatabaseSeeder.php,

<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;

use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

class DatabaseSeeder extends Seeder
{

/**
* List of applications to add.
*/
private $permissions = [
'role-list',
'role-create',
'role-edit',
'role-delete',
'product-list',
'product-create',
'product-edit',
'product-delete'
];


/**
* Seed the application's database.
*/
public function run(): void
{
foreach ($this->permissions as $permission) {
Permission::create(['name' => $permission]);
}

// Create admin User and assign the role to him.
$user = User::create([
'name' => 'Prevail Ejimadu',
'email' => 'test@example.com',
'password' => Hash::make('password')
]);

$role = Role::create(['name' => 'Admin']);

$permissions = Permission::pluck('id', 'id')->all();

$role->syncPermissions($permissions);

$user->assignRole([$role->id]);
}
}

Great! we did it. Next, we should make sure that we have connected our database, then let’s run our migration and seed the db with the command below,

php artisan migrate --seed

Step 7: Create Routes

Open your route/web.php

<?php

use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\RoleController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\ProductController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/

Route::get('/', function () {
return view('welcome');
});

Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');

Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');

// Our resource routes
Route::resource('roles', RoleController::class);
Route::resource('users', UserController::class);
Route::resource('products', ProductController::class);
});

require __DIR__.'/auth.php';

Step 8: Add Controllers

Let’s get this done, we’ll create 3 controllers, and they would be resource controllers. Let’s quickly do this with command below.

php artisan make:controller RoleController -r
php artisan make:controller UserController -r
php artisan make:controller ProductController -r

Let’s Open the app/Http/Controllers/ProductController.php

<?php

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
function __construct()
{
$this->middleware(['permission:product-list|product-create|product-edit|product-delete'], ['only' => ['index', 'show']]);
$this->middleware(['permission:product-create'], ['only' => ['create', 'store']]);
$this->middleware(['permission:product-edit'], ['only' => ['edit', 'update']]);
$this->middleware(['permission:product-delete'], ['only' => ['destroy']]);
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$products = Product::latest()->paginate(50);
return view('products.index', compact('products'));
}

/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('products.create');
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
request()->validate([
'name' => 'required',
'detail' => 'required',
]);

Product::create($request->all());

return redirect()->route('products.index')
->with('success', 'Product created successfully.');
}

/**
* Display the specified resource.
*
* @param \App\Product $product
* @return \Illuminate\Http\Response
*/
public function show(Product $product)
{
return view('products.show', compact('product'));
}

/**
* Show the form for editing the specified resource.
*
* @param \App\Product $product
* @return \Illuminate\Http\Response
*/
public function edit(Product $product)
{
return view('products.edit', compact('product'));
}

/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Product $product
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Product $product)
{
request()->validate([
'name' => 'required',
'detail' => 'required',
]);

$product->update($request->all());

return redirect()->route('products.index')
->with('success', 'Product updated successfully');
}

/**
* Remove the specified resource from storage.
*
* @param \App\Product $product
* @return \Illuminate\Http\Response
*/
public function destroy(Product $product)
{
$product->delete();

return redirect()->route('products.index')
->with('success', 'Product deleted successfully');
}
}

Let’s Open the app/Http/Controllers/RoleController.php

<?php

namespace App\Http\Controllers;


use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

class RoleController extends Controller
{

function __construct()
{
$this->middleware(['permission:role-list|role-create|role-edit|role-delete'], ['only' => ['index', 'store']]);
$this->middleware(['permission:role-create'], ['only' => ['create', 'store']]);
$this->middleware(['permission:role-edit'], ['only' => ['edit', 'update']]);
$this->middleware(['permission:role-delete'], ['only' => ['destroy']]);
}

public function index(Request $request)
{
$roles = Role::orderBy('id', 'DESC')->paginate(5);
return view('roles.index', compact('roles'));
}

public function create()
{
$permission = Permission::get();
return view('roles.create', compact('permission'));
}

public function store(Request $request)
{
$this->validate($request, [
'name' => 'required|unique:roles,name',
'permission' => 'required',
]);

$role = Role::create(['name' => $request->input('name')]);
$role->syncPermissions($request->input('permission'));

return redirect()->route('roles.index')
->with('success', 'Role created successfully');
}

public function show($id)
{
$role = Role::find($id);
$rolePermissions = Permission::join("role_has_permissions", "role_has_permissions.permission_id", "=", "permissions.id")
->where("role_has_permissions.role_id", $id)
->get();

return view('roles.show', compact('role', 'rolePermissions'));
}

public function edit($id)
{
$role = Role::find($id);
$permission = Permission::get();
$rolePermissions = DB::table("role_has_permissions")->where("role_has_permissions.role_id", $id)
->pluck('role_has_permissions.permission_id', 'role_has_permissions.permission_id')
->all();

return view('roles.edit', compact('role', 'permission', 'rolePermissions'));
}

public function update(Request $request, $id)
{
$this->validate($request, [
'name' => 'required',
'permission' => 'required',
]);

$role = Role::find($id);
$role->name = $request->input('name');
$role->save();

$role->syncPermissions($request->input('permission'));

return redirect()->route('roles.index')
->with('success', 'Role updated successfully');
}

public function destroy($id)
{
DB::table("roles")->where('id', $id)->delete();
return redirect()->route('roles.index')
->with('success', 'Role deleted successfully');
}
}

Finally, let’s Open the app/Http/Controllers/UserController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use Spatie\Permission\Models\Role;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;

class UserController extends Controller
{

public function index(Request $request)
{
$data = User::latest()->paginate(5);
return view('users.index',compact('data'));
}

public function create()
{
$roles = Role::pluck('name','name')->all();
return view('users.create',compact('roles'));
}

public function store(Request $request)
{
$this->validate($request, [
'name' => 'required',
'email' => 'required|email|unique:users,email',
'password' => 'required|same:confirm-password',
'roles' => 'required'
]);

$input = $request->all();
$input['password'] = Hash::make($input['password']);

$user = User::create($input);
$user->assignRole($request->input('roles'));

return redirect()->route('users.index')
->with('success','User created successfully');
}

public function show($id)
{
$user = User::find($id);
return view('users.show',compact('user'));
}

public function edit($id)
{
$user = User::find($id);
$roles = Role::pluck('name','name')->all();
$userRole = $user->roles->pluck('name','name')->all();

return view('users.edit',compact('user','roles','userRole'));
}

public function update(Request $request, $id)
{
$this->validate($request, [
'name' => 'required',
'email' => 'required|email|unique:users,email,'.$id,
'password' => 'same:confirm-password',
'roles' => 'required'
]);

$input = $request->all();
if(!empty($input['password'])){
$input['password'] = Hash::make($input['password']);
}else{
$input = Arr::except($input,array('password'));
}

$user = User::find($id);
$user->update($input);
DB::table('model_has_roles')->where('model_id',$id)->delete();

$user->assignRole($request->input('roles'));

return redirect()->route('users.index')
->with('success','User updated successfully');
}

public function destroy($id)
{
User::find($id)->delete();
return redirect()->route('users.index')
->with('success','User deleted successfully');
}
}

Step 9: Create Blade File

Let us add blade files for each action.

Note: If you’re caught up or something, All the code can be found in the public repository.

  • For master layout, we will create a master.blade.php file
  • For the Users Module, we will create index.blade.php, create.blade.php, edit.blade.php, and show.blade.php files inside resources/views/users
  • For the Roles Module, we will create index.blade.php, create.blade.php, edit.blade.php, and show.blade.php files inside resources/views/roles
  • For the Product Module, we will create index.blade.php, create.blade.php, edit.blade.php, and show.blade.php files inside resources/views/products

resources/views/layouts/master.blade.php


<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Laravel Spatie Permission</title>
<script src="{{ asset('js/app.js') }}" defer></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>

<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light navbar-laravel">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
Laravel Spatie Tutorial
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>

<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto">

@guest
<li><a class="nav-link" href="{{ route('login') }}">{{ ('Login') }} </a></li>
<li><a class="nav-link" href="{{ route('register') }}">{{ ('Register') }}</a></li>
@else
<li><a class="nav-link" href="{{ route('users.index') }}">Manage Users</a></li>
<li><a class="nav-link" href="{{ route('roles.index') }}">Manage Role</a></li>
<li><a class="nav-link" href="{{ route('products.index') }}">Manage Product</a></li>
<li class="ms-3 nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>

<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ ('Logout') }}
</a>

<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>

<main class="py-4 bg-light">
<div class="container">
@yield('content')
</div>
</main>
</div>
</body>
</html>

resources/views/users/index.blade.php


@extends('layouts.master')


@section('content')
<div class="row">
<div class="col-lg-12 margin-tb mb-4">
<div class="pull-left">
<h2>Users Management
<div class="float-end">
<a class="btn btn-success" href="{{ route('users.create') }}"> Create New User</a>
</div>
</h2>
</div>
</div>
</div>


@if ($message = Session::get('success'))
<div class="alert alert-success my-2">
<p>{{ $message }}</p>
</div>
@endif


<table class="table table-bordered table-hover table-striped">
<tr>
<th>Name</th>
<th>Email</th>
<th>Roles</th>
<th width="280px">Action</th>
</tr>
@foreach ($data as $key => $user)
<tr>
<td>{{ $user->name }}</td>
<td>{{ $user->email }}</td>
<td>
@if(!empty($user->getRoleNames()))
@foreach($user->getRoleNames() as $v)
<label class="badge badge-secondary text-dark">{{ $v }}</label>
@endforeach
@endif
</td>
<td>
<a class="btn btn-info" href="{{ route('users.show',$user->id) }}">Show</a>
<a class="btn btn-primary" href="{{ route('users.edit',$user->id) }}">Edit</a>
<a class="btn btn-success" href="{{ route('users.destroy',$user->id) }}"> Delete</a>
</td>
</tr>
@endforeach
</table>
@endsection

resources/views/users/create.blade.php

@extends('layouts.master')

@section('content')
<div class="row">
<div class="col-lg-12 margin-tb mb-4">
<div class="pull-left">
<h2>Create New User
<div class="float-end">
<a class="btn btn-primary" href="{{ route('users.index') }}"> Back</a>
</div>
</h2>
</div>
</div>
</div>

@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>Whoops!</strong> There were some problems with your input.<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

<form action="{{ route('users.store') }}" method="POST">
@csrf
<div class="row">
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Name:</strong>
<input type="text" name="name" class="form-control" placeholder="Name">
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Email:</strong>
<input type="email" name="email" class="form-control" placeholder="Email">
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Password:</strong>
<input type="password" name="password" class="form-control" placeholder="Password">
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Confirm Password:</strong>
<input type="password" name="confirm-password" class="form-control" placeholder="Confirm Password">
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Role:</strong>
<select class="form-control multiple" multiple name="roles[]">
@foreach ($roles as $role)
<option value="{{ $role }}">{{ $role }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-xs-12 mb-3 text-center">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>

@endsection

resources/views/users/show.blade.php


@extends('layouts.master')


@section('content')
<div class="row">
<div class="col-lg-12 margin-tb mb-4">
<div class="pull-left">
<h2>Show User</h2>
<div class="float-end">
<a class="btn btn-primary" href="{{ route('users.index') }}"> Back</a>
</div>
</div>
</div>
</div>


<div class="row">
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Name:</strong>
{{ $user->name }}
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Email:</strong>
{{ $user->email }}
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Roles:</strong>
@if(!empty($user->getRoleNames()))
@foreach($user->getRoleNames() as $v)
<label class="badge badge-secondary text-dark">{{ $v }}</label>
@endforeach
@endif
</div>
</div>
</div>
@endsection

resources/views/users/edit.blade.php

@extends('layouts.master')


@section('content')
<div class="row">
<div class="col-lg-12 margin-tb mb-4">
<div class="pull-left">
<h2>Edit User
<div class="float-end">
<a class="btn btn-primary" href="{{ route('users.index') }}"> Back</a>
</div>
</h2>
</div>
</div>
</div>


@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>Whoops!</strong> There were some problems with your input.<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif


<form action="{{ route('users.update', $user->id) }}" method="PATCH">
@csrf
<div class="row">
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Name:</strong>
<input type="text" value="{{ $user->name }}" name="name" class="form-control"
placeholder="Name">
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Email:</strong>
<input type="email" name="email" value="{{ $user->email }}" class="form-control"
placeholder="Email">
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Password:</strong>
<input type="password" name="password" class="form-control"
placeholder="Password">
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Confirm Password:</strong>
<input type="password" name="confirm-password" class="form-control"
placeholder="Confirm Password">
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Role:</strong>
<select class="form-control multiple" multiple name="roles[]">
@foreach ($roles as $role)
<option value="{{ $role }}">{{ $role }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-xs-12 mb-3 text-center">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>

@endsection

resources/views/roles/index.blade.php

@extends('layouts.master')

@section('content')
<div class="row">
<div class="col-lg-12 margin-tb mb-4">
<div class="pull-left">
<h2>Role Management
<div class="float-end">
@can('role-create')
<a class="btn btn-success" href="{{ route('roles.create') }}"> Create New Role</a>
@endcan
</div>
</h2>
</div>
</div>
</div>

@if ($message = Session::get('success'))
<div class="alert alert-success">
<p>{{ $message }}</p>
</div>
@endif

<table class="table table-striped table-hover">
<tr>
<th>Name</th>
<th width="280px">Action</th>
</tr>
@foreach ($roles as $key => $role)
<tr>
<td>{{ $role->name }}</td>
<td>
<form action="{{ route('roles.destroy', $role->id) }}" method="POST">
<a class="btn btn-info" href="{{ route('roles.show', $role->id) }}">Show</a>
@can('role-edit')
<a class="btn btn-primary" href="{{ route('roles.edit', $role->id) }}">Edit</a>
@endcan


@csrf
@method('DELETE')
@can('product-delete')
<button type="submit" class="btn btn-danger">Delete</button>
@endcan
</form>
</td>
</tr>
@endforeach
</table>

{!! $roles->render() !!}
@endsection

resources/views/roles/show.blade.php

@extends('layouts.master')


@section('content')
<div class="row">
<div class="col-lg-12 margin-tb mb-4">
<div class="pull-left">
<h2> Show Role
<div class="float-end">
<a class="btn btn-primary" href="{{ route('roles.index') }}"> Back</a>
</div>
</h2>
</div>
</div>
</div>


<div class="row">
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Name:</strong>
{{ $role->name }}
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Permissions:</strong>
@if (!empty($rolePermissions))
@foreach ($rolePermissions as $v)
<label class="label label-secondary text-dark">{{ $v->name }},</label>
@endforeach
@endif
</div>
</div>
</div>
@endsection

resources/views/roles/create.blade.php

@extends('layouts.master')

@section('content')
<div class="row">
<div class="col-lg-12 margin-tb mb-4">
<div class="pull-left">
<h2>Create New Role
<div class="float-end">
<a class="btn btn-primary" href="{{ route('roles.index') }}"> Back</a>
</div>
</h2>
</div>
</div>
</div>

@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>Whoops!</strong> There were some problems with your input.<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

<form action="{{ route('roles.store') }}" method="POST">
@csrf
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Name:</strong>
<input type="text" name="name" class="form-control" placeholder="Name">
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12">
<div class="form-group">
<strong>Permission:</strong>
<br />
@foreach ($permission as $value)
<label>
<input type="checkbox" name="permission[]" value="{{ $value->id }}" class="name">
{{ $value->name }}</label>
<br />
@endforeach
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 text-center">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
@endsection

resources/views/roles/edit.blade.php

@extends('layouts.master')


@section('content')
<div class="row">
<div class="col-lg-12 margin-tb">
<div class="pull-left">
<h2>Edit Role
<div class="float-end">
<a class="btn btn-primary" href="{{ route('roles.index') }}"> Back</a>
</div>
</h2>
</div>
</div>
</div>


@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>Whoops!</strong> There were some problems with your input.<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

<form action="{{ route('roles.update', $role->id) }}" method="PATCH">
@csrf
<div class="row">
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Name:</strong>
<input type="text" value="{{ $role->name }}" name="name" class="form-control"
placeholder="Name">
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Permission:</strong>
<br />
@foreach ($permission as $value)
<label>
<input type="checkbox" @if (in_array($value->id, $rolePermissions)) checked @endif name="permission[]"
value="{{ $value->id }}" class="name">
{{ $value->name }}</label>
<br />
@endforeach
</div>
</div>
<div class="col-xs-12 mb-3 text-center">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>


@endsection

resources/views/products/index.blade.php


@extends('layouts.master')

@section('content')
<div class="row">
<div class="col-lg-12 margin-tb mb-4">
<div class="pull-left">
<h2>Products
<div class="float-end">
@can('product-create')
<a class="btn btn-success" href="{{ route('products.create') }}"> Create New Product</a>
@endcan
</div>
</h2>
</div>
</div>
</div>


@if ($message = Session::get('success'))
<div class="alert alert-success">
<p>{{ $message }}</p>
</div>
@endif

<table class="table table-striped table-hover">
<tr>
<th>Name</th>
<th>Details</th>
<th width="280px">Action</th>
</tr>
@foreach ($products as $product)
<tr>
<td>{{ $product->name }}</td>
<td>{{ $product->detail }}</td>
<td>
<form action="{{ route('products.destroy',$product->id) }}" method="POST">
<a class="btn btn-info" href="{{ route('products.show',$product->id) }}">Show</a>
@can('product-edit')
<a class="btn btn-primary" href="{{ route('products.edit',$product->id) }}">Edit</a>
@endcan


@csrf
@method('DELETE')
@can('product-delete')
<button type="submit" class="btn btn-danger">Delete</button>
@endcan
</form>
</td>
</tr>
@endforeach
</table>

@endsection

resources/views/products/show.blade.php


@extends('layouts.master')


@section('content')
<div class="row">
<div class="col-lg-12 margin-tb mb-4">
<div class="pull-left">
<h2> Show Product
<div class="float-end">
<a class="btn btn-primary" href="{{ route('products.index') }}"> Back</a>
</div>
</h2>
</div>
</div>
</div>


<div class="row">
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Name:</strong>
{{ $product->name }}
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Details:</strong>
{{ $product->detail }}
</div>
</div>
</div>
@endsection

resources/views/products/create.blade.php

@extends('layouts.master')

@section('content')
<div class="row">
<div class="col-lg-12 margin-tb mb-4">
<div class="pull-left">
<h2>Create New Product
<div class="float-end">
<a class="btn btn-primary" href="{{ route('products.index') }}"> Back</a>
</div>
</h2>
</div>
</div>
</div>

@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>Whoops!</strong> There were some problems with your input.<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

<form action="{{ route('products.store') }}" method="POST">
@csrf
<div class="row">
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Name:</strong>
<input type="text" name="name" class="form-control" placeholder="Name">
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Detail:</strong>
<textarea class="form-control" style="height:150px" name="detail" placeholder="Detail"></textarea>
</div>
</div>
<div class="col-xs-12 mb-3 text-center">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
@endsection

resources/views/products/edit.blade.php

@extends('layouts.master')

@section('content')
<div class="row">
<div class="col-lg-12 margin-tb mb-4">
<div class="pull-left">
<h2>Edit Product

<div class="float-end">
<a class="btn btn-primary" href="{{ route('products.index') }}"> Back</a>
</div>
</h2>
</div>
</div>
</div>

@if ($errors->any())
<div class="alert alert-danger">
<strong>Whoops!</strong> There were some problems with your input.<br><br>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

<form action="{{ route('products.update', $product->id) }}" method="POST">
@csrf
@method('PUT')

<div class="row">
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Name:</strong>
<input type="text" name="name" value="{{ $product->name }}" class="form-control"
placeholder="Name">
</div>
</div>
<div class="col-xs-12 mb-3">
<div class="form-group">
<strong>Detail:</strong>
<textarea class="form-control" style="height:150px" name="detail" placeholder="Detail">{{ $product->detail }}</textarea>
</div>
</div>
<div class="col-xs-12 mb-3 text-center">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
@endsection

Now serve your application

php artisan serve

Open another terminal tab and start vite development serve

npm run dev

Then login with the admin user details ‘test@example.com and password’ and go to the url

http://127..0.0.1:8000/users

And voila! You’d get a working application with role creation and permission management.

Note: Because we used breeze to authenticate, you’ll see blade components designed with tailwind css, if you’re comfortable with those you can modify that. I created a separate master and layout because this tutorial is s focused on using spatie permission rather than frontend.

What Next?

We have seen how to use Laravel’s blade @can to specify section where users can use based on their permission. We also saw how to use the roles and permissions as middleware via their controller. If you want to use it inside a route, it’s the same method, see this below:

Route::group(['middleware' => ['role:super-admin','permission:publish articles']], function () {
//
});

Route::get('/', function(){
//
})->middleware(['can:edit articles']);

You can specify multiple roles or permissions with a | (pipe) character, which is treated as OR:

Route::group(['middleware' => ['role:super-admin|writer']], function () {
//
});

Route::group(['middleware' => ['permission:publish articles|edit articles']], function () {
//
});

Conclusion

This article got longer than I expected, but there’s still a lot to cover on how to use roles and permissions in laravel using spatie’s package.

There are so many other ways to utilize one of Spatie’s most popular package, you can see all the details in their official documentation here.. You can always check out the repo, and try out different things as much as possible.

Stay tuned!!! I will be back with some more cool Laravel tutorials in the next article. I hope you liked the article. Don’t forget to follow me 😇 and give some clap 👏. And if you have any questions feel free to comment.

Thank you.

--

--

Chimeremze Prevail Ejimadu

Laravel Developer + Writer + Entrepreneur + Open source contributor + Founder + Open for projects & collaborations. Hit FOLLOW ⤵