Courses

NEW! How to Create Laravel Package: Step-by-Step Example

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:

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

app/Http/Controllers/RoleController.php:

1namespace App\Http\Controllers;
2 
3use Illuminate\Http\Request;
4use Spatie\Permission\Models\Role;
5use Spatie\Permission\Models\Permission;
6 
7class RoleController extends Controller
8{
9 public function index()
10 {
11 $roles = Role::withCount('permissions')->get();
12 
13 return view('roles.index', compact('roles'));
14 }
15 
16 public function create() {
17 $permissions = Permission::pluck('name', 'id');
18 
19 return view('roles.create', compact('permissions'));
20 }
21 
22 public function store(Request $request)
23 {
24 $request->validate([
25 'name' => ['required', 'string', 'unique:roles'],
26 'permissions' => ['array'],
27 ]);
28 
29 $role = Role::create(['name' => $request->input('name')]);
30 
31 $role->givePermissionTo($request->input('permissions'));
32 
33 return redirect()->route('roles.index');
34 }
35 
36 public function edit(Role $role)
37 {
38 $permissions = Permission::pluck('name', 'id');
39 
40 return view('roles.edit', compact('role', 'permissions'));
41 }
42 
43 public function update(Request $request, Role $role)
44 {
45 $request->validate([
46 'name' => ['required', 'string', 'unique:roles,name,' . $role->id],
47 'permissions' => ['array'],
48 ]);
49 
50 $role->update(['name' => $request->input('name')]);
51 
52 $role->syncPermissions($request->input('permissions'));
53 
54 return redirect()->route('roles.index');
55 }
56 
57 public function destroy(Role $role)
58 {
59 $role->delete();
60 
61 return redirect()->route('roles.index');
62 }
63}

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

1@extends('layouts.app')
2 
3@section('content')
4 <h1 class="text-xl font-semibold text-gray-900">Roles</h1>
5 <a href="{{ route('roles.create') }}">Add Role</a>
6 
7 <table class="min-w-full divide-y divide-gray-300">
8 <thead class="bg-gray-50">
9 <tr>
10 <th>Name</th>
11 <th>Permissions</th>
12 <th scope="col"></th>
13 </tr>
14 </thead>
15 <tbody class="divide-y divide-gray-200 bg-white">
16 @forelse ($roles as $role)
17 <tr>
18 <td>{{ $role->name }}</td>
19 <td>{{ $role->permissions_count }}</td>
20 <td>
21 <a href="{{ route('roles.edit', $role) }}">Edit</a>
22 
23 <form action="{{ route('roles.destroy', $role) }}"
24 method="POST"
25 onsubmit="return confirm('Are you sure?')"
26 class="inline-block">
27 @csrf
28 @method('DELETE')
29 <button type="submit">Delete</button>
30 </form>
31 </td>
32 </tr>
33 @empty
34 <tr>
35 <td colspan="3">No roles found.</td>
36 </tr>
37 @endforelse
38 </tbody>
39 </table>
40@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:

1@extends('layouts.app')
2 
3@section('content')
4 <h1 class="text-xl font-semibold text-gray-900">Create Role</h1>
5 
6 @if ($errors->any())
7 <div class="text-red-500 text-sm mb-4">
8 <ul>
9 @foreach ($errors->all() as $error)
10 <li>{{ $error }}</li>
11 @endforeach
12 </ul>
13 </div>
14 @endif
15 
16 <form action="{{ route('roles.store') }}" method="POST">
17 @csrf
18 <div>
19 <label for="name">Name</label>
20 <input type="text" name="name" id="name" value="{{ old('name') }}" required autofocus>
21 </div>
22 
23 @if ($permissions->count())
24 <div class="mt-4">
25 <label for="permissions">Permissions</label>
26 
27 @foreach ($permissions as $id => $name)
28 <input type="checkbox" name="permissions[]" id="permission-{{ $id }}"
29 value="{{ $id }}" @checked(in_array($id, old('permissions', [])))>
30 <label for="permission-{{ $id }}">{{ $name }}</label>
31 <br />
32 @endforeach
33 </div>
34 @endif
35 
36 <div class="mt-4">
37 <button type="submit">Save</button>
38 </div>
39 </form>
40@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.

avatar

Povilas sir thank you so much for this tutorial I was trying to dive into package development but didn't know where to start. Hope this tutorial will make this journey easy

👍 1
avatar

I second this message :)