Back to Course |
How to Create Laravel Package: Step-by-Step Example

Starting Point: Package Functionality

We start from the situation where we have this Permission Editor as a functionality inside of our Laravel application.

Laravel package roles list

Laravel package roles create

We have a typical MVC structure: routes, controllers, views, and validation.

routes/web.php:

Route::resource('roles', \App\Http\Controllers\RoleController::class);
Route::resource('permissions', \App\Http\Controllers\PermissionController::class);

app/Http/Controllers/RoleController.php:

namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
 
class RoleController extends Controller
{
public function index()
{
$roles = Role::withCount('permissions')->get();
 
return view('roles.index', compact('roles'));
}
 
public function create() {
$permissions = Permission::pluck('name', 'id');
 
return view('roles.create', compact('permissions'));
}
 
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'unique:roles'],
'permissions' => ['array'],
]);
 
$role = Role::create(['name' => $request->input('name')]);
 
$role->givePermissionTo($request->input('permissions'));
 
return redirect()->route('roles.index');
}
 
public function edit(Role $role)
{
$permissions = Permission::pluck('name', 'id');
 
return view('roles.edit', compact('role', 'permissions'));
}
 
public function update(Request $request, Role $role)
{
$request->validate([
'name' => ['required', 'string', 'unique:roles,name,' . $role->id],
'permissions' => ['array'],
]);
 
$role->update(['name' => $request->input('name')]);
 
$role->syncPermissions($request->input('permissions'));
 
return redirect()->route('roles.index');
}
 
public function destroy(Role $role)
{
$role->delete();
 
return redirect()->route('roles.index');
}
}

resources/views/roles/index.blade.php:

@extends('layouts.app')
 
@section('content')
<h1 class="text-xl font-semibold text-gray-900">Roles</h1>
<a href="{{ route('roles.create') }}">Add Role</a>
 
<table class="min-w-full divide-y divide-gray-300">
<thead class="bg-gray-50">
<tr>
<th>Name</th>
<th>Permissions</th>
<th scope="col"></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($roles as $role)
<tr>
<td>{{ $role->name }}</td>
<td>{{ $role->permissions_count }}</td>
<td>
<a href="{{ route('roles.edit', $role) }}">Edit</a>
 
<form action="{{ route('roles.destroy', $role) }}"
method="POST"
onsubmit="return confirm('Are you sure?')"
class="inline-block">
@csrf
@method('DELETE')
<button type="submit">Delete</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="3">No roles found.</td>
</tr>
@endforelse
</tbody>
</table>
@endsection

In the Blade snippet above, I intentionally skipped some <div> parts and CSS classes, so you would focus on the structure and not how it looks visually.

The form to create the role:

resources/views/roles/create.blade.php:

@extends('layouts.app')
 
@section('content')
<h1 class="text-xl font-semibold text-gray-900">Create Role</h1>
 
@if ($errors->any())
<div class="text-red-500 text-sm mb-4">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
 
<form action="{{ route('roles.store') }}" method="POST">
@csrf
<div>
<label for="name">Name</label>
<input type="text" name="name" id="name" value="{{ old('name') }}" required autofocus>
</div>
 
@if ($permissions->count())
<div class="mt-4">
<label for="permissions">Permissions</label>
 
@foreach ($permissions as $id => $name)
<input type="checkbox" name="permissions[]" id="permission-{{ $id }}"
value="{{ $id }}" @checked(in_array($id, old('permissions', [])))>
<label for="permission-{{ $id }}">{{ $name }}</label>
<br />
@endforeach
</div>
@endif
 
<div class="mt-4">
<button type="submit">Save</button>
</div>
</form>
@endsection

Again, some sections are "stripped down" for simplicity.

So, our goal is to turn these two Route Resources into a package, so developers would be able to run composer require our/package and then navigate to, for example, /permission-editor to see the same functionality.

Our package will have a prerequisite of spatie/laravel-permission installed and configured, but we will get to that later in the tutorial. Now, let's start actually creating the package.