Hi Dev,
In this Laravel 12 tutorial, you will learn how to set up roles and permissions using the Spatie Laravel Permission package. This package provides a simple yet powerful way to manage Access Control Lists (ACL) in Laravel applications, allowing you to define custom user roles, assign permissions, and control access to specific modules.
We will build a real-world example with three core modules:
- User Management – Manage registered users and assign roles.
- Role Management – Create, edit, and delete roles with specific permissions.
- Post Management – Restrict post listing, creation, editing, and deletion based on assigned permissions.
When a new user registers, they won’t have any roles assigned by default. From the User Management panel, you can assign the Admin role to yourself or create custom roles such as Editor or Viewer. Permissions like `role-list`, `role-create`, `role-edit`, `role-delete`, `post-list`, `post-create`, `post-edit`, and `post-delete` can be linked directly to users or roles.
By following this guide, you’ll be able to:
- Install and configure Spatie Laravel Permission in Laravel 12.
- Create and manage roles dynamically.
- Assign permissions to roles and users.
- Secure routes, controllers, and views based on permissions.
With this setup, your Laravel 12 project will have a fully functional ACL system that’s scalable, secure, and easy to maintain — perfect for any application needing role-based access control.
Laravel 12 ACL – Roles and Permissions Example (Step-by-Step Guide)
Follow these steps to implement Roles and Permissions in Laravel 12 using the Spatie Laravel Permission package.
-
Step 1: Install Laravel 12
Set up a fresh Laravel 12 project using Composer.
-
Step 2: Install Spatie Laravel Permission
Require the
spatie/laravel-permission
package via Composer to manage roles and permissions. -
Step 3: Create Product Migration
Generate a migration file for the
products
table and define necessary fields. -
Step 4: Create Models
Set up models for
User
,Role
,Permission
, andProduct
with the proper relationships. -
Step 5: Add Middleware
Register and configure middleware for role and permission checks to secure routes.
-
Step 6: Set Up Authentication
Use Laravel Breeze, Jetstream, or UI authentication scaffolding for user login and registration.
-
Step 7: Define Routes
Add web routes for user, role, and product management with role-based restrictions.
-
Step 8: Create Controllers
Build controllers to handle CRUD operations for users, roles, permissions, and products.
-
Step 9: Add Blade Views
Design the Blade templates for listing, creating, and editing users, roles, and products.
-
Step 10: Seed Permissions and Admin User
Create a seeder to insert default roles, permissions, and an admin account.
-
Final Step: Run Laravel Application
Start the development server and test your role-based access control system.
Role list

Role Create

User List

Post List

First step, we need to install Laravel 12 project. Now open your terminal and run below command
composer create-project laravel/laravel example-appStep 2: Install spatie/laravel-permission Package
Install the Spatie package via Composer to easily manage user roles and permissions in your Laravel 12 application. Open your terminal and run the following command to install the Spatie package for managing roles and permissions in Laravel 12.
composer require spatie/laravel-permissionStep 3: Publish Configuration and Migrations
If you want to customize the Spatie package settings, publish its configuration and migration files. This will create a config/permission.php
file and the necessary migration files.
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
After publishing, run the migration command below to create the required database tables:
php artisan migrateStep 3: Create Product Migration
In this step, you will generate a migration file for the posts
table. This migration will define the database structure for storing product information in your application.
php artisan make:migration create_posts_table
database/migrations/2025_07_24_093522_create_posts_table.php
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->string('slug')->unique(); $table->text('description'); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('posts'); } };
Now, run the below command to migrate posts table:
php artisan migrateStep 4: Create and Update Models
In this step, we will set up Eloquent models for the User
and Product
tables. Laravel includes a default User
model in fresh installations, so you only need to update it with the necessary role and permission logic. For the Product
model, create a new file to manage product-related data.
app/Models/User.php
<?php namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ use HasFactory, Notifiable, HasRoles; /** * The attributes that are mass assignable. * * @var list*/ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for serialization. * * @var list */ protected $hidden = [ 'password', 'remember_token', ]; /** * Get the attributes that should be cast. * * @return array */ protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; } }
Now you will run below command to generate Post model.
php artisan make:model Post
app/Models/Post.php
<?php namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ use HasFactory, Notifiable, HasRoles; /** * The attributes that are mass assignable. * * @var listStep 5: Add Middleware*/ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for serialization. * * @var list */ protected $hidden = [ 'password', 'remember_token', ]; /** * Get the attributes that should be cast. * * @return array */ protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; } }
The Spatie package comes with built-in middleware for checking user roles and permissions. These can be easily registered in your Laravel application. The available middleware are:
role
– Checks if the user has a specific role.permission
– Checks if the user has a specific permission.role_or_permission
– Checks if the user has either the specified role or permission.
To use these, register them in the bootstrap/app.php
file as shown below:
bootstrap/app.php
<?php use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { $middleware->alias([ 'role' => \Spatie\Permission\Middleware\RoleMiddleware::class, 'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class, 'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class ]); }) ->withExceptions(function (Exceptions $exceptions) { // })->create();Step 6: Set Up Authentication
To add authentication in Laravel 12, we will use the laravel/ui
package for generating login, registration, and password reset scaffolding.
1. Install Laravel UI Package:
composer require laravel/ui
2. Generate Auth Scaffolding with Bootstrap:
php artisan ui bootstrap --auth
3. Install NPM Packages:
npm install
4. Build Frontend Assets:
npm run build
Once completed, Laravel will have ready-made authentication views and routes, and your login/register pages will have a clean Bootstrap-based layout.
Step 7: Define RoutesIn this step, we will register routes for the Users
, Products
, and Roles
modules. These routes will be protected with authentication middleware, and you can also apply role/permission middleware as needed for added security.
Update the routes/web.php
file with the following code:
<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\HomeController; use App\Http\Controllers\RoleController; use App\Http\Controllers\UserController; use App\Http\Controllers\PostController; Route::get('/', function () { return view('welcome'); }); Auth::routes(); Route::get('/home', [HomeController::class, 'index'])->name('home'); Route::group(['middleware' => ['auth']], function() { Route::resource('roles', RoleController::class); Route::resource('users', UserController::class); Route::resource('posts', PostController::class); });
Here, the auth
middleware ensures that only logged-in users can access the role, user, and product management modules.
In this step, we have added three controllers for the users module, products module, and roles module. So, you can create three controllers as below:
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 DB; use Hash; use Illuminate\Support\Arr; use Illuminate\View\View; use Illuminate\Http\RedirectResponse; class UserController extends Controller { public function index(Request $request): View { $data = User::latest()->paginate(5); return view('users.index',compact('data')) ->with('i', ($request->input('page', 1) - 1) * 5); } public function create(): View { $roles = Role::pluck('name','name')->all(); return view('users.create',compact('roles')); } public function store(Request $request): RedirectResponse { $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): View { $user = User::find($id); return view('users.show',compact('user')); } public function edit($id): View { $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): RedirectResponse { $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): RedirectResponse { User::find($id)->delete(); return redirect()->route('users.index') ->with('success','User deleted successfully'); } }app/Http/Controllers/PostController.php
<?php namespace App\Http\Controllers; use App\Models\Post; use Illuminate\Http\Request; use Illuminate\View\View; use Illuminate\Http\RedirectResponse; class PostController extends Controller { function __construct() { $this->middleware('permission:post-list|post-create|post-edit|post-delete', ['only' => ['index','show']]); $this->middleware('permission:post-create', ['only' => ['create','store']]); $this->middleware('permission:post-edit', ['only' => ['edit','update']]); $this->middleware('permission:post-delete', ['only' => ['destroy']]); } public function index(): View { $posts = Post::latest()->paginate(5); return view('posts.index',compact('posts')) ->with('i', (request()->input('page', 1) - 1) * 5); } public function create(): View { return view('posts.create'); } public function store(Request $request): RedirectResponse { request()->validate([ 'title' => 'required', 'slug' => 'required|unique:posts,slug', 'description' => 'required', ]); Post::create($request->all()); return redirect()->route('posts.index') ->with('success','Post created successfully.'); } public function show(Post $post): View { return view('posts.show',compact('post')); } public function edit(Post $post): View { return view('posts.edit',compact('post')); } public function update(Request $request, Post $post): RedirectResponse { request()->validate([ 'title' => 'required', 'slug' => 'required|unique:posts,slug,'.$post->id, 'description' => 'required', ]); $post->update($request->all()); return redirect()->route('posts.index') ->with('success','Post updated successfully'); } public function destroy(Post $post): RedirectResponse { $post->delete(); return redirect()->route('posts.index') ->with('success','Post deleted successfully'); } }app/Http/Controllers/RoleController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use Spatie\Permission\Models\Role; use Spatie\Permission\Models\Permission; use DB; use Illuminate\View\View; use Illuminate\Http\RedirectResponse; 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): View { $roles = Role::orderBy('id','DESC')->paginate(5); return view('roles.index',compact('roles')) ->with('i', ($request->input('page', 1) - 1) * 5); } public function create(): View { $permission = Permission::get(); return view('roles.create',compact('permission')); } public function store(Request $request): RedirectResponse { $this->validate($request, [ 'name' => 'required|unique:roles,name', 'permission' => 'required', ]); $permissionsID = array_map( function($value) { return (int)$value; }, $request->input('permission') ); $role = Role::create(['name' => $request->input('name')]); $role->syncPermissions($permissionsID); return redirect()->route('roles.index') ->with('success','Role created successfully'); } public function show($id): View { $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): View { $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): RedirectResponse { $this->validate($request, [ 'name' => 'required', 'permission' => 'required', ]); $role = Role::find($id); $role->name = $request->input('name'); $role->save(); $permissionsID = array_map( function($value) { return (int)$value; }, $request->input('permission') ); $role->syncPermissions($permissionsID); return redirect()->route('roles.index') ->with('success','Role updated successfully'); } public function destroy($id): RedirectResponse { DB::table("roles")->where('id',$id)->delete(); return redirect()->route('roles.index') ->with('success','Role deleted successfully'); } }Step 9: Add Blade Files resources/views/users/index.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Table Design with Tailwind CSS</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Create Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">User Management</h1> <a href="{{ route('users.create') }}" class="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> Create New User </a> </div> <!-- Table --> <div class="overflow-x-auto bg-white shadow-md rounded-lg"> <table class="min-w-full divide-y divide-gray-200"> <thead class="bg-gray-50"> <tr> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> </tr> </thead> <tbody class="bg-white divide-y divide-gray-200"> <!-- Sample Row 1 --> @foreach ($data as $key => $user) <tr> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ ++$key }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $user->name }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $user->email }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2 flex items-center"> <a class="bg-blue-500 hover:bg-blue-600 text-white py-1 px-3 rounded transition duration-200 no-underline" href="{{ route('users.show',$user->id) }}">Show</a> <a class="bg-yellow-500 hover:bg-yellow-600 text-white py-1 px-3 rounded transition duration-200 no-underline" href="{{ route('users.edit',$user->id) }}">Edit</a> <form method="POST" action="{{ route('users.destroy', $user->id) }}" onsubmit="return confirm('Are you sure?');" class="inline-block"> @csrf @method('DELETE') <button type="submit" class="bg-red-500 hover:bg-red-600 text-white py-1 px-3 rounded transition duration-200">Delete</button> </form> </td> </tr> @endforeach </tbody> </table> </div> <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>resources/views/users/create.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Create New User</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Back Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">Create New User</h1> <a href="{{ route('users.index') }}" class="bg-gray-500 hover:bg-gray-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> ← Back </a> </div> @if (count($errors) > 0) <div class="mb-6 p-4 bg-red-100 border border-red-300 text-red-800 rounded-lg"> <strong>Whoops!</strong> There were some problems with your input.<br><br> <ul class="list-disc list-inside"> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <!-- Form Card --> <div class="bg-white shadow-md rounded-lg p-6"> <form method="POST" action="{{ route('users.store') }}"> @csrf <div class="space-y-6"> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Name:</label> <input type="text" name="name" placeholder="Name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Email:</label> <input type="email" name="email" placeholder="Email" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Password:</label> <input type="password" name="password" placeholder="Password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Confirm Password:</label> <input type="password" name="confirm-password" placeholder="Confirm Password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Role:</label> <select name="roles[]" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" multiple="multiple"> @foreach ($roles as $value => $label) <option value="{{ $value }}">{{ $label }}</option> @endforeach </select> </div> <div class="text-center pt-4"> <button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-6 rounded-lg transition duration-200"> Submit </button> </div> </div> </form> </div> <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>resources/views/users/edit.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Edit User</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Back Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">Edit User</h1> <a href="{{ route('users.index') }}" class="bg-gray-500 hover:bg-gray-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> ← Back </a> </div> @if (count($errors) > 0) <div class="mb-6 p-4 bg-red-100 border border-red-300 text-red-800 rounded-lg"> <strong>Whoops!</strong> There were some problems with your input.<br><br> <ul class="list-disc list-inside"> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <!-- Form Card --> <div class="bg-white shadow-md rounded-lg p-6"> <form method="POST" action="{{ route('users.update', $user->id) }}"> @csrf @method('PUT') <div class="space-y-6"> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Name:</label> <input type="text" name="name" placeholder="Name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" value="{{ $user->name }}"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Email:</label> <input type="email" name="email" placeholder="Email" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" value="{{ $user->email }}"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Password:</label> <input type="password" name="password" placeholder="Password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Confirm Password:</label> <input type="password" name="confirm-password" placeholder="Confirm Password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Role:</label> <select name="roles[]" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" multiple="multiple"> @foreach ($roles as $value => $label) <option value="{{ $value }}" {{ isset($userRole[$value]) ? 'selected' : ''}}> {{ $label }} </option> @endforeach </select> </div> <div class="text-center pt-4"> <button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-6 rounded-lg transition duration-200"> Update </button> </div> </div> </form> </div> <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>resources/views/users/show.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Show User</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Back Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">Show User</h1> <a href="{{ route('users.index') }}" class="bg-gray-500 hover:bg-gray-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> ← Back </a> </div> <!-- User Details Card --> <div class="bg-white shadow-md rounded-lg p-6"> <div class="space-y-6"> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Name:</label> <p class="text-lg text-gray-900">{{ $user->name }}</p> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Email:</label> <p class="text-lg text-gray-900">{{ $user->email }}</p> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Roles:</label> <div class="flex flex-wrap gap-2"> @if(!empty($user->getRoleNames())) @foreach($user->getRoleNames() as $v) <span class="bg-green-100 text-green-800 text-sm font-medium px-3 py-1 rounded-full">{{ $v }}</span> @endforeach @else <span class="text-gray-500">No roles assigned</span> @endif </div> </div> </div> </div> <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>resources/views/posts/index.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Posts Management</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Create Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">Posts Management</h1> @can('post-create') <a href="{{ route('posts.create') }}" class="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> Create New Post </a> @endcan </div> @if (session('success')) <div class="mb-6 p-4 bg-green-100 border border-green-300 text-green-800 rounded-lg"> {{ session('success') }} </div> @endif <!-- Table Card --> <div class="overflow-x-auto bg-white shadow-md rounded-lg"> <table class="min-w-full divide-y divide-gray-200"> <thead class="bg-gray-50"> <tr> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">No</th> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Title</th> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Slug</th> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> </tr> </thead> <tbody class="bg-white divide-y divide-gray-200"> @foreach ($posts as $post) <tr> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ ++$i }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $post->title }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $post->slug }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $post->description }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2 flex items-center"> <a class="bg-blue-500 hover:bg-blue-600 text-white py-1 px-3 rounded transition duration-200 no-underline" href="{{ route('posts.show',$post->id) }}">Show</a> @can('post-edit') <a class="bg-yellow-500 hover:bg-yellow-600 text-white py-1 px-3 rounded transition duration-200 no-underline" href="{{ route('posts.edit',$post->id) }}">Edit</a> @endcan @can('post-delete') <form method="POST" action="{{ route('posts.destroy', $post->id) }}" onsubmit="return confirm('Are you sure?');" class="inline-block"> @csrf @method('DELETE') <button type="submit" class="bg-red-500 hover:bg-red-600 text-white py-1 px-3 rounded transition duration-200">Delete</button> </form> @endcan </td> </tr> @endforeach </tbody> </table> </div> <!-- Pagination --> @if(isset($posts) && method_exists($posts, 'links')) <div class="mt-6 flex justify-center"> {!! $posts->links('pagination::bootstrap-5') !!} </div> @endif <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>resources/views/posts/create.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Add New Post</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Back Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">Add New Post</h1> <a href="{{ route('posts.index') }}" class="bg-gray-500 hover:bg-gray-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> ← Back </a> </div> @if ($errors->any()) <div class="mb-6 p-4 bg-red-100 border border-red-300 text-red-800 rounded-lg"> <strong>Whoops!</strong> There were some problems with your input.<br><br> <ul class="list-disc list-inside"> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <!-- Form Card --> <div class="bg-white shadow-md rounded-lg p-6"> <form action="{{ route('posts.store') }}" method="POST"> @csrf <div class="space-y-6"> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Title:</label> <input type="text" name="title" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="Title"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Slug:</label> <input type="text" name="slug" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="Slug"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Description:</label> <textarea class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" style="height:150px" name="description" placeholder="Description"></textarea> </div> <div class="text-center pt-4"> <button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-6 rounded-lg transition duration-200"> Submit </button> </div> </div> </form> </div> <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>resources/views/posts/edit.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Edit Post</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Back Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">Edit Post</h1> <a href="{{ route('posts.index') }}" class="bg-gray-500 hover:bg-gray-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> ← Back </a> </div> @if ($errors->any()) <div class="mb-6 p-4 bg-red-100 border border-red-300 text-red-800 rounded-lg"> <strong>Whoops!</strong> There were some problems with your input.<br><br> <ul class="list-disc list-inside"> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <!-- Form Card --> <div class="bg-white shadow-md rounded-lg p-6"> <form action="{{ route('posts.update',$post->id) }}" method="POST"> @csrf @method('PUT') <div class="space-y-6"> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Title:</label> <input type="text" name="title" value="{{ $post->title }}" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="Title"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Slug:</label> <input type="text" name="slug" value="{{ $post->slug }}" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="Slug"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Description:</label> <textarea class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" style="height:150px" name="description" placeholder="Description">{{ $post->description }}</textarea> </div> <div class="text-center pt-4"> <button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-6 rounded-lg transition duration-200"> Update </button> </div> </div> </form> </div> <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>resources/views/posts/show.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Show Post</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Back Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">Show Post</h1> <a href="{{ route('posts.index') }}" class="bg-gray-500 hover:bg-gray-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> ← Back </a> </div> <!-- Post Details Card --> <div class="bg-white shadow-md rounded-lg p-6"> <div class="space-y-6"> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Title:</label> <p class="text-lg text-gray-900">{{ $post->title }}</p> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Slug:</label> <p class="text-lg text-gray-900">{{ $post->slug }}</p> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Description:</label> <p class="text-lg text-gray-900">{{ $post->description }}</p> </div> </div> </div> <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>resources/views/roles/index.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Roles Management</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Create Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">Roles Management</h1> @can('role-create') <a href="{{ route('roles.create') }}" class="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> Create New Role </a> @endcan </div> @if (session('success')) <div class="mb-6 p-4 bg-green-100 border border-green-300 text-green-800 rounded-lg"> {{ session('success') }} </div> @endif <!-- Table Card --> <div class="overflow-x-auto bg-white shadow-md rounded-lg"> <table class="min-w-full divide-y divide-gray-200"> <thead class="bg-gray-50"> <tr> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">No</th> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> </tr> </thead> <tbody class="bg-white divide-y divide-gray-200"> @foreach ($roles as $key => $role) <tr> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ ++$i }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $role->name }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2 flex items-center"> <a class="bg-blue-500 hover:bg-blue-600 text-white py-1 px-3 rounded transition duration-200 no-underline" href="{{ route('roles.show',$role->id) }}">Show</a> @can('role-edit') <a class="bg-yellow-500 hover:bg-yellow-600 text-white py-1 px-3 rounded transition duration-200 no-underline" href="{{ route('roles.edit',$role->id) }}">Edit</a> @endcan @can('role-delete') <form method="POST" action="{{ route('roles.destroy', $role->id) }}" onsubmit="return confirm('Are you sure?');" class="inline-block"> @csrf @method('DELETE') <button type="submit" class="bg-red-500 hover:bg-red-600 text-white py-1 px-3 rounded transition duration-200">Delete</button> </form> @endcan </td> </tr> @endforeach </tbody> </table> </div> <!-- Pagination --> @if(isset($roles) && method_exists($roles, 'links')) <div class="mt-6 flex justify-center"> {!! $roles->links('pagination::bootstrap-5') !!} </div> @endif <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>resources/views/roles/create.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Create New Role</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Back Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">Create New Role</h1> <a href="{{ route('roles.index') }}" class="bg-gray-500 hover:bg-gray-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> ← Back </a> </div> @if (count($errors) > 0) <div class="mb-6 p-4 bg-red-100 border border-red-300 text-red-800 rounded-lg"> <strong>Whoops!</strong> There were some problems with your input.<br><br> <ul class="list-disc list-inside"> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <!-- Form Card --> <div class="bg-white shadow-md rounded-lg p-6"> <form method="POST" action="{{ route('roles.store') }}"> @csrf <div class="space-y-6"> <div> <label class="block text-sm font-medium text-gray-700 mb-2" for="name">Name:</label> <input type="text" name="name" id="name" placeholder="Name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" value="{{ old('name') }}"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Permission:</label> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3"> @foreach($permission as $value) <label class="inline-flex items-center"> <input type="checkbox" name="permission[{{$value->id}}]" value="{{$value->id}}" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"> <span class="ml-2 text-sm text-gray-700">{{ $value->name }}</span> </label> @endforeach </div> </div> <div class="text-center pt-4"> <button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-6 rounded-lg transition duration-200"> Submit </button> </div> </div> </form> </div> <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>resources/views/roles/edit.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Edit Role</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Back Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">Edit Role</h1> <a href="{{ route('roles.index') }}" class="bg-gray-500 hover:bg-gray-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> ← Back </a> </div> @if (count($errors) > 0) <div class="mb-6 p-4 bg-red-100 border border-red-300 text-red-800 rounded-lg"> <strong>Whoops!</strong> There were some problems with your input.<br><br> <ul class="list-disc list-inside"> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <!-- Form Card --> <div class="bg-white shadow-md rounded-lg p-6"> <form method="POST" action="{{ route('roles.update', $role->id) }}"> @csrf @method('PUT') <div class="space-y-6"> <div> <label class="block text-sm font-medium text-gray-700 mb-2" for="name">Name:</label> <input type="text" name="name" id="name" placeholder="Name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" value="{{ $role->name }}"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Permission:</label> <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3"> @foreach($permission as $value) <label class="inline-flex items-center"> <input type="checkbox" name="permission[{{$value->id}}]" value="{{$value->id}}" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" {{ in_array($value->id, $rolePermissions) ? 'checked' : ''}}> <span class="ml-2 text-sm text-gray-700">{{ $value->name }}</span> </label> @endforeach </div> </div> <div class="text-center pt-4"> <button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-6 rounded-lg transition duration-200"> Update </button> </div> </div> </form> </div> <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>resources/views/roles/show.blade.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Show Role</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="bg-gray-100"> <div class="w-1/2 mx-auto p-6"> <!-- Heading and Back Button --> <div class="flex justify-between items-center mb-6"> <h1 class="text-3xl font-bold text-gray-800">Show Role</h1> <a href="{{ route('roles.index') }}" class="bg-gray-500 hover:bg-gray-600 text-white font-semibold py-2 px-4 rounded-lg transition duration-200 no-underline"> ← Back </a> </div> <!-- Role Details Card --> <div class="bg-white shadow-md rounded-lg p-6"> <div class="space-y-6"> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Name:</label> <p class="text-lg text-gray-900">{{ $role->name }}</p> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Permissions:</label> <div class="flex flex-wrap gap-2"> @if(!empty($rolePermissions)) @foreach($rolePermissions as $v) <span class="bg-green-100 text-green-800 text-sm font-medium px-3 py-1 rounded-full">{{ $v->name }}</span> @endforeach @else <span class="text-gray-500">No permissions assigned</span> @endif </div> </div> </div> </div> <!-- Footer --> <footer class="text-center py-4"> <p><span class="bg-yellow-100 text-gray-800 px-2 py-1 rounded">Tutorials by <a href="https://stuffcoder.com" class="underline hover:text-gray-600">stuffcoder.com</a></span></p> </footer> </div> </body> </html>Step 10: Create Seeder for Permissions
In this step, we will generate a database seeder to insert predefined permissions into the system. These permissions can later be assigned to specific roles or users. You can also expand the list to match your project requirements.
Default Permissions:
- role-list
- role-create
- role-edit
- role-delete
- post-list
- post-create
- post-edit
- post-delete
1. Create the Seeder File:
php artisan make:seeder PermissionTableSeeder
2. Update the Seeder Code:
Edit the database/seeders/PermissionTableSeeder.php
file and add the following code:
<?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use Spatie\Permission\Models\Permission; class PermissionTableSeeder extends Seeder { /** * Run the database seeds. */ public function run(): void { $permissions = [ 'role-list', 'role-create', 'role-edit', 'role-delete', 'post-list', 'post-create', 'post-edit', 'post-delete' ]; foreach ($permissions as $permission) { Permission::create(['name' => $permission]); } } }
php artisan make:seeder CreateAdminUserSeederdatabase/seeders/CreateAdminUserSeeder.php
<?php namespace Database\Seeders; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use App\Models\User; use Spatie\Permission\Models\Role; use Spatie\Permission\Models\Permission; class CreateAdminUserSeeder extends Seeder { /** * Run the database seeds. */ public function run(): void { $user = User::create([ 'name' => 'Stuffcoder Admin', 'email' => 'admin@gmail.com', 'password' => bcrypt('123456') ]); $role = Role::create(['name' => 'Admin']); $permissions = Permission::pluck('id','id')->all(); $role->syncPermissions($permissions); $user->assignRole([$role->id]); } }Final Step: Seed Permission & Admin User and Run the Application
Once all configurations, migrations, and permissions are in place, you can create an admin account using the admin user seeder.
1. Run the Permission Seeder:
php artisan db:seed --class=PermissionTableSeeder
2. Run the Admin User Seeder:
php artisan db:seed --class=CreateAdminUserSeeder
2. Start the Laravel Development Server:
php artisan serve
3. Access the Application in Your Browser:
http://localhost:8000
You can now log in using the default admin credentials:
- Email: admin@gmail.com
- Password: 123456
After logging in, you’ll be able to manage users, roles, and permissions directly from the Laravel 12 application interface.
I hope It will help you....