yajra/laravel-breadcrumbs
A lightweight breadcrumb package for Laravel that helps you define and render breadcrumb trails in Blade views. It supports route-based breadcrumb definitions and simple customization, making it easy to add structured navigation to your application without complex configuration.
composer require yajra/laravel-breadcrumbs
Before writing a single line of code, run:
composer show yajra/laravel-breadcrumbs
Read the require block. The illuminate/support constraint is the whole compatibility story for a package with a "Laravel 5.4+" claim — if it doesn't cover your major, stop and treat the source as a reference implementation instead. On Laravel 5.5+ the service provider is auto-discovered; there is no config/app.php edit unless you're on 5.4.
Where to look first, in order:
vendor/yajra/laravel-breadcrumbs/views/ — the shipped templates (bootstrap2/3/4 lineage). This is your real API surface for markup.config/breadcrumbs.php after php artisan vendor:publish — two keys matter: view (which template renders) and files (where definitions are loaded from, default routes/breadcrumbs.php).src/BreadcrumbsServiceProvider.php — confirms how the definitions file is required and whether a Breadcrumbs facade alias is registered (relevant if you already have one).First use case — a flat two-level trail. Create routes/breadcrumbs.php:
use Yajra\Breadcrumbs\Facades\Breadcrumbs;
Breadcrumbs::for('home', fn ($trail) => $trail->push('Home', route('home')));
Breadcrumbs::for('posts.show', function ($trail, $post) {
$trail->parent('home');
$trail->push($post->title, route('posts.show', $post));
});
Render it from one layout partial, not from controllers:
{{-- resources/views/partials/breadcrumbs.blade.php --}}
{{ Breadcrumbs::renderIfExists() }}
renderIfExists() resolves the current route name automatically and renders nothing when no definition matches — a safe default for a layout that gets included everywhere. Breadcrumbs::render('posts.show', $post) is the explicit form for show pages where the layout doesn't know the model.
Then immediately wrap it:
{{-- resources/views/components/breadcrumbs.blade.php --}}
<nav aria-label="Breadcrumb">{{ Breadcrumbs::renderIfExists() }}</nav>
Everything above the package — layouts, tests, presenters — should reference <x-breadcrumbs />, never the facade. That single indirection is the difference between "removing the package is a file edit" and "removing the package is a refactor."
Group definitions by section, mirroring your route files. One routes/breadcrumbs.php gets unwieldy fast; keep admin, catalogue, and account blocks separated by comments and review them alongside route changes. A missing definition is a silent empty nav, so co-locating the diff is your cheapest regression control.
Chain with parent(), not by duplicating pushes. parent() inserts the parent's trail at the point of the call, so call it before push():
Breadcrumbs::for('catalogue.products.show', function ($trail, $category, $product) {
$trail->parent('catalogue.categories.show', $category); // inserts Home > Category
$trail->push($product->name, route('catalogue.products.show', [$category, $product]));
});
Callback arguments map positionally to route parameters. Declare defaults for optional parameters in the closure signature so definitions don't blow up when a segment is absent.
Use exists() to guard optional segments. Where a trail is genuinely conditional (draft previews, unlisted pages), check Breadcrumbs::exists('route.name') and branch rather than relying on the exception path.
Pass a data payload for anything the template needs beyond title/url. The third argument to push() lands on $breadcrumb->data, which is the supported way to add icons, counters, or "current" markers without stuffing HTML into the label:
$trail->push('Users', route('admin.users.index'), ['count' => $userCount]);
Custom templates: publish the views to resources/views/vendor/breadcrumbs/ and edit, or point config('breadcrumbs.view') at your own Blade file (e.g. partials.breadcrumbs-nav). Prefer the config pointer — it keeps the trail markup in your app tree where it's greppable and reviewed, and survives composer update.
JSON-LD is yours to add. The package emits nav markup only. Generate the trail once and map it:
$trail = Breadcrumbs::generate(); // array of objects with ->title / ->url
// map to schema.org BreadcrumbList, emit in a <script type="application/ld+json">
If SEO rich results are the actual requirement, budget this as separate work — the package saves you nothing here.
Localise in the definition, not the view:
$trail->push(__('nav.home'), route('home'));
Definitions are plain PHP evaluated at render time, so __() composes normally, and locale switches per request.
Testing. One feature test per major route group, asserting the rendered trail end-to-end through the layout:
$this->get(route('catalogue.products.show', [$category, $product]))
->assertSee('Home')
->assertSee($category->name)
->assertSee($product->name);
Add DB::enableQueryLog() assertions for any definition that resolves a model — see the N+1 note below.
Migration pattern. Replacing an existing breadcrumb implementation? Only routes/breadcrumbs.php and the template change. Keep the <nav><ol> contract identical so the visual diff is zero, and migrate section by section (settings → admin → catalogue) rather than site-wide, so a missing definition degrades one page instead of every page.
<nav> — the site looks fine and navigation is gone. Decide per surface: render() (explicit, throws on a bad name) in section layouts where you want dev-time noise, renderIfExists() in the global layout where a 500 is worse than a missing crumb.davejamesmiller/laravel-breadcrumbs, and diglactic/laravel-breadcrumbs is the maintained fork of the same lineage. Two of them bound at once shadow each other's facade and produce confusing view behaviour. Enforce a single breadcrumb owner in code review; if you're on Laravel 10/11, that owner should probably not be this repo.{{ }} on titles — keep it that way in any custom view. Labels routinely interpolate model names and user input, so {!! $breadcrumb->title !!} is a stored-XSS vector with a friendly face. If a designer needs an icon in a crumb, add it in the template as markup around the escaped title, not inside the title.catalogue.products.show or its {product} parameter breaks the definition silently (empty trail) or loudly (Missing required parameter). Add "update routes/breadcrumbs.php" to your route-change checklist; the feature tests from the previous section are what actually catch it.route() vs URL::to(). Use route() with the model/params — it encodes parameters, respects domain/subdirectory config, and keeps signed or localised URL logic in one place.composer update is the main way this package breaks your build. Treat it like a vendored dependency with an audit date.Breadcrumbs::renderIfExists() inside <x-breadcrumbs /> — one call site to grep, one place to swap to an in-house implementation, one file to edit if you ever vendor the source into app/Support/.src/ into app/Support/Breadcrumbs, keep the same facade name, run the tests. The equivalent implementation is roughly 30–50 lines; proving that to yourself converts the supply-chain risk from an open question into a known, cheap exit.How can I help you explore Laravel packages today?