arcanedev/breadcrumbs
ArcaneDev Breadcrumbs is a Laravel package for defining and rendering flexible breadcrumb trails. Build crumbs from routes, closures, or arrays, customize templates, and output accessible navigation with minimal setup.
Check the install gate before you write any code. The latest tagged release is from November 2020, so its composer.json almost certainly pins illuminate/support/illuminate/routing to Laravel 6–8. On a modern Laravel you will get a dependency-resolution failure, not a runtime bug. Confirm with:
composer require arcanedev/breadcrumbs --dry-run
If it refuses, stop and decide between a fork with widened constraints (see Gotchas) and a maintained alternative (diglactic/laravel-breadcrumbs is the closest named-trail equivalent) before you build conventions on top of this API.
Once it resolves, the first read is three files in the vendor tree:
src/BreadcrumbsServiceProvider.php — what is auto-discovered, what config/views are published, whether a Blade directive is registered.src/Breadcrumbs.php — the fluent surface (push, pushFromRoute, pushFromAction, render).resources/views/*.blade.php + config/breadcrumbs.php — the view and name/separator keys you will actually override.Publish so you own the template and config:
php artisan vendor:publish --provider="Arcanedev\Breadcrumbs\BreadcrumbsServiceProvider"
First use case — a three-level trail in a controller, rendered in the layout:
use Arcanedev\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 page
}
{{-- layouts/app.blade.php --}}
{!! Breadcrumbs::render() !!}
Get that rendering, then immediately move the definition out of the controller (next section) — controllers should not own navigation.
Route-keyed trail map + middleware (the pattern worth adopting). Because this package is a linear chain you push onto, not named trails, the cleanest day-to-day structure is a single declarative map keyed by route name, applied by one middleware. Controllers and views stay untouched, and adding a page means adding four lines of config.
// config/breadcrumbs-map.php
return [
'users.index' => [
['route' => 'home', 'title' => 'Home'],
['route' => 'users.index', 'title' => 'Users'],
],
'users.show' => [
['route' => 'home', 'title' => 'Home'],
['route' => 'users.index', 'title' => 'Users'],
['route' => 'users.show', 'title' => fn ($user) => $user->name],
],
];
Derive from the route, don't hardcode URLs. pushFromRoute() resolves through Laravel's URL generator, so route model binding, localized slugs, and tenant domains all keep working:
Breadcrumbs::pushFromRoute('users.index', [], 'Users');
Breadcrumbs::pushFromRoute('users.show', [$user], $user->name);
Build once, in one place. Pick exactly one of: a middleware, a View::composer on your layout, or a base-controller trait. Never two — the service is a stateful singleton and you will get duplicated crumbs.
Translate titles at the boundary, not in the view: Breadcrumbs::push(__('nav.users'), route('users.index')).
Custom template — copy the published view into resources/views/vendor/breadcrumbs/ and switch config('breadcrumbs.view'). This is your hook for Tailwind classes, wrapping <nav aria-label="Breadcrumb">, or a <ol> with microdata.
Emit JSON-LD from the same trail so the visible navigation and the SEO markup can never diverge:
$items = collect(Breadcrumbs::render()->???);
If the trail isn't exposed as an array, keep the trail definition in a small BreadcrumbTrail support class that both the facade call and the JSON-LD builder consume.
Testing. Assert on the rendered page, not on package internals: $this->get('/users/1')->assertSeeInOrder(['Home', 'Users', 'Jane']). Add one characterization test per distinct trail shape; that test is what catches behavioural drift on any future upgrade.
"illuminate/support": "^6|^7|^8|^9|^10|^11") usually "just works" because the API touches only the URL generator and the view factory — but that is a fork you now maintain, and you must re-run your breadcrumb characterization tests on each Laravel major. Budget it, or pick the maintained alternative.Breadcrumbs.php for a reset/flush method before you rely on re-builds.pushFromRoute() throws. Unknown route names or missing required parameters raise RouteNotFoundException / UrlGenerationException — a 500 on a page whose only crime is a broken nav. Guard with Route::has() when the map is data-driven, or wrap in try/catch and degrade to a title-only crumb.route on the terminal item) and enforce it in the custom view.push() without escaping in the template, you have an XSS vector on every page. Escape at push time or fix the view — and write a test with <script> in a model name.view and the separator/name keys are read at render time, so config(['breadcrumbs.view' => ...]) mid-request works for per-section layouts — but keep it to one convention or the nav becomes inconsistent between screens.diglactic/laravel-breadcrumbs, you cannot define a trail once and render it from several pages; every page pushes its own chain. That's why the route map above matters — without it, breadcrumb definition scatters across controllers and rots.Breadcrumbs — you'll be patching the vendor tree.How can I help you explore Laravel packages today?