watson/breadcrumbs
Lightweight Laravel package for defining and rendering breadcrumb trails. Add breadcrumbs in PHP, attach them to routes, and output accessible HTML with Blade. Supports current-page detection, custom templates, nested trails, and simple configuration for Laravel applications.
Install the package with Composer:
composer require watson/breadcrumbs
On Laravel 5.5+ the Watson\Breadcrumbs\ServiceProvider and the Breadcrumbs facade are auto-discovered. If you are on an older version, register the provider in config/app.php and add the alias:
'Breadcrumbs' => Watson\Breadcrumbs\Facades\Breadcrumbs::class,
Publish the config so you can point the package at your own view (the default is a Bootstrap-flavoured breadcrumbs::breadcrumbs template):
php artisan vendor:publish --provider="Watson\Breadcrumbs\ServiceProvider"
The first use case is almost always a controller method or a view composer that pushes a trail and then renders it in your layout:
use Watson\Breadcrumbs\Facades\Breadcrumbs;
public function show(User $user)
{
Breadcrumbs::push('Home', route('home'));
Breadcrumbs::push('Users', route('users.index'));
Breadcrumbs::push($user->name); // no URL = current/active crumb
return view('users.show', compact('user'));
}
{{-- layouts/app.blade.php --}}
{!! Breadcrumbs::render() !!}
Start by reading src/Breadcrumbs.php (it is tiny — push, render, clear, and the crumb array) and the published config/breadcrumbs.php. That is the entire surface area; there is no route-scanning magic to learn.
Centralise the trail per resource in a view composer. Rather than scattering push() calls through controllers, bind a composer in a service provider and switch on the route name:
View::composer('layouts.app', function ($view) {
$crumbs = Breadcrumbs::forRoute(request()->route());
// or, using the package directly:
Breadcrumbs::push('Home', route('home'));
if (request()->routeIs('users.*')) {
Breadcrumbs::push('Users', route('users.index'));
}
});
This keeps every controller free of presentation logic and gives you one place to change the hierarchy.
Use the fluent return value. push() returns the breadcrumb instance, so you can build a chain in one expression:
Breadcrumbs::push('Home', '/')
->push('Blog', route('posts.index'))
->push($post->title);
Traverse a model's ancestry. For category trees or threaded comments, walk the parent chain in a loop and push as you go (reversing first so the trail reads root → leaf):
$ancestors = $category->ancestors()->reverse();
foreach ($ancestors as $ancestor) {
Breadcrumbs::push($ancestor->name, route('categories.show', $ancestor));
}
Breadcrumbs::push($category->name);
Wrap it in a Blade component so your templates stay declarative and you can swap the package later without touching views:
<x-breadcrumbs :items="Breadcrumbs::toArray()" />
Assert on output in feature tests — it is a cheap regression guard on your navigation:
$this->get('/users/1')
->assertSeeInOrder(['Home', 'Users', 'Taylor']);
Breadcrumbs::clear() (or re-bind the instance) at the start of each request. A RefreshBreadcrumbs middleware or a before hook in your composer is the cleanest fix.render() returns HTML. Use {!! !!} rather than {{ }}, and make sure your custom view escapes the titles with e() — user-supplied model names in a crumb are an XSS vector otherwise.active flag) to render plain text instead of an anchor; check the view's $crumb['url'] === null handling when you customise the template.route()/url(), never string concatenation. It keeps the trail correct when you add locale prefixes, subdomain routing, or change a URI; hardcoded /users/1 breadcrumbs silently rot.dd(Breadcrumbs::toArray()) at the top of the layout is the fastest debug.view. Publish it, point it at breadcrumbs.app in your own resources/views/components and you fully control markup, separators, and microdata/JSON-LD.illuminate/support constraint in a fork, or accept that it is ~100 lines you could vendor into app/Support/Breadcrumbs.php — worth considering given there are zero dependents and no recent activity.push only when a section is visible) or caching, do it in your composer; there is no event system to hook into.How can I help you explore Laravel packages today?