bjerke/laravel-bread
Laravel Bread is a small package for adding breadcrumb navigation to Laravel applications. Define breadcrumb trails and render them in Blade views to give users a clear sense of location and hierarchy, with simple configuration and helper methods.
This is a boilerplate/scaffold, not a runtime framework. Treat it the way you'd treat a starter kit: you pull it in, generate or copy code out of it, then own that code. The day-one goal is to get one model exposed over a REST API with the full Browse/Read/Edit/Add/Delete surface, then replicate the pattern.
Install and inspect.
composer require bjerke/laravel-bread:^4.0
The package is back under active maintenance (v4.0.1), but there's a multi-year gap between the 2022 releases and the 4.x line, so treat 4.x as a new baseline rather than a continuation. Before writing code:
4.0.0 release notes/changelog first. A major bump after a long dormancy almost always carries breaking changes (namespace moves, PHP/Laravel minimums, renamed config keys) that no older tutorial or blog post will reflect.composer.json in the package for illuminate/* constraints and the PHP floor against your Laravel version.src/ and read the base controller/abstract class first — that's the contract you'll be extending.stubs/, config/, and src/Console/ — boilerplate packages usually ship an artisan generator and publishable config.php artisan vendor:publish --provider="Bjerke\Bread\..." (confirm the provider FQCN from src/) to pull config/stubs into your app.First use case: expose a simple CRUD model — e.g. Post — as a REST resource. Define the route with the package's route helper or a standard Route::apiResource, point it at a controller that extends the package's base controller, and implement the five BREAD handlers. Give Post a relation (e.g. author) so you exercise the relation search/display-field path that 4.0.1 touched.
Where to look next: the README's example controller, then src/ for the trait/base class that implements the shared browse/read/edit/add/delete logic. If the package registers routes or middleware automatically, check php artisan route:list before hand-rolling routes.
Extend, don't compose. Boilerplate packages of this shape usually expose an abstract controller or a trait that provides the five operations. The idiomatic flow is:
class PostController extends BreadController
{
protected string $model = \App\Models\Post::class;
protected string $resource = PostResource::class;
protected array $searchable = ['title', 'body', 'author.name'];
protected array $with = ['author'];
}
Verify the actual property/method names in src/ — this is the part of the package you'll be reading rather than remembering.
Relations need a display field. 4.0.1 fixed a typo in the searchField handling for relations, which is the mechanism that auto-sets the field used to render/search a related model (so a Browse listing can show author.name instead of an id, and search across it). When you expose a relation:
Keep BREAD separate from CRUD. The "Browse" operation is the differentiator from plain resource controllers: it's a listing endpoint with pagination, filtering, sorting, and eager-loading. Put filter validation in a dedicated form request and scope it to a query builder rather than letting arbitrary ?filter= keys hit where().
Route shape. Prefer explicit resource routes over auto-registration so you can attach per-verb middleware:
Route::apiResource('posts', PostController::class)
->middleware(['auth:sanctum']);
If the package ships a route macro or registration helper, use it only if it doesn't hide middleware you need to customize.
Validation at the boundary. Wrap Add/Edit in FormRequest classes with authorize() returning your policy check. Boilerplate defaults rarely include authorization — that's on you, and it's the most common place these scaffolds are insecure out of the box.
Responses via API Resources. If the package has its own response formatting, prefer to bypass it and return JsonResource/ResourceCollection so your API shape stays under your control and is consistent across BREAD and non-BREAD endpoints.
Reuse the pattern. Once one controller works, generate the rest with php artisan make: or copy-and-adjust. Because the boilerplate is thin, the pattern is your real asset — document it in your repo (an app/Http/Controllers/Api/README.md or a template) so new endpoints look identical.
Testing pattern. Because BREAD is five verbs, test them as a table: browse (with and without filters, and with a relation search), read (found, 404, unauthorized), add (valid, invalid, unauthorized), edit (same), delete (found, soft-deleted). A single parameterized test per model catches regressions across the whole surface.
^4.0) in composer.json, read src/ before relying on implicit behavior, and don't assume a quick turnaround on issues. If you find yourself patching internals, fork it into app/ or a local path repo.src/ rather than trusting memory.app/Http/Controllers/Api/, delete the require, and own it. That removes version-compatibility risk entirely.illuminate/* constraints and PHP floor before installing. If they're capped below your Laravel version, Composer will refuse or you'll be running against untested internals.PUT/PATCH and POST; "Read" is GET /{id}; "Browse" is GET /. Don't accidentally collapse Browse and Read into one action — filtering logic doesn't belong on the single-record endpoint.searchField resolution for relations, snapshot-test your Browse responses after upgrading: a relation that previously returned an id or null may now return the display value (or vice versa if you had a manual override fighting the default). Same for filter behaviour on relation columns.SoftDeletes, GET /{id} on a trashed record 404s by default. Decide explicitly between withTrashed() for admin read vs. hard 404 for public, and test it.$model->fill($request->all()), replace it with $request->validated() immediately. This is the single highest-severity default in most scaffold packages.?sort=id;drop table or arbitrary column names — validate sortable/filterable columns (including relation dotted paths) against a whitelist property on the controller.php artisan route:list --path=api before touching controller code. If responses look wrong, dump the resolved controller instance in Route::getRoutes() to confirm you're hitting the package base rather than a Laravel default.FormRequests. Avoid overriding deep base methods unless you've read them.How can I help you explore Laravel packages today?