slope-it/breadcrumb-bundle
SlopeIt BreadcrumbBundle makes it easy to define, manage, and render breadcrumb navigation in PHP applications. Build breadcrumb trails with a simple API and output them in your templates, keeping site navigation clear and consistent with minimal configuration.
Before you install: this is a Symfony bundle, not a Laravel package. composer require slope-it/breadcrumb-bundle in a Laravel app won't register anything — there's no service provider, no facade, no Blade directive. If you're working in Laravel, the reusable part is the pattern (named breadcrumb collections, builder service, Twig/Blade partial override), and diglactic/laravel-breadcrumbs is the direct equivalent. The rest of this assumes you're in a Symfony app (6.x/7.x, PHP 8.1+).
Minimal path:
composer require slope-it/breadcrumb-bundle — Flex auto-registers the bundle; if not, add it to config/bundles.php.Resources/config/services.yaml (to see the manager's service ID and any config keys), then the BreadcrumbManager / BreadcrumbItem classes. Two minutes there saves an hour of guessing at method names.// src/Controller/ProductController.php
public function show(Product $product, BreadcrumbManager $breadcrumbs): Response
{
$breadcrumbs
->addItem('Home', ['path' => 'home'])
->addItem('Products', ['path' => 'product_list'])
->addItem($product->getName());
return $this->render('product/show.html.twig', ['product' => $product]);
}
{# templates/layout.html.twig #}
<nav aria-label="breadcrumb">{{ slope_it_breadcrumb_render('default') }}</nav>
Confirm the Twig function name and collection argument with bin/console debug:twig — that's the fastest sanity check if the README snippet is slightly stale.
First real use case to target: a catalog/detail page (Home > Category > Product). Static pages don't need this bundle; anything with an entity-derived label does.
One collection per navigation region. The main reason to pick this over a hand-rolled Twig partial is named collections: main, admin, docs each keep an independent item list on the same manager. Define the names as constants (BreadcrumbCollection::MAIN) so a typo can't silently produce an empty <ol>.
Wrap the manager in your own builder. Keep controllers thin and put the "how does a domain object become a crumb" logic in one place:
final class ProductBreadcrumbs
{
public function __construct(private BreadcrumbManager $breadcrumbs) {}
public function forProduct(Product $product): void
{
$this->breadcrumbs
->addItem('Home', ['path' => 'home'])
->addItem($product->getCategory()->getName(), [
'path' => 'category_show',
'params' => ['slug' => $product->getCategory()->getSlug()],
])
->addItem($product->getName());
}
}
That wrapper is also the seam you'd port to Laravel later, since it has no Symfony dependencies beyond the manager.
Choose one build location per route. Either the controller builds the trail, or a kernel.controller / kernel.request listener builds it section-wide. Doing both is how you get duplicated "Home" entries. For a whole /admin/* area, a listener is less repetitive; for entity pages, the controller has the data.
Attribute/annotation route if available. If the bundle ships a #[Breadcrumb]-style attribute, use it for static trails (Home > Legal > Privacy) where there's nothing dynamic to compute. Fall back to imperative code the moment a segment needs an entity label — mixing both on one route is where confusion starts.
Override the template, not the internals. Copy the bundle's template into templates/bundles/SlopeItBreadcrumbBundle/... and change the markup there (Bootstrap breadcrumb-item, JSON-LD BreadcrumbList, aria-current="page" on the last item). That keeps your styling decisions in your repo and survives minor bundle releases.
Test the trail, not the HTML. Assert getItems() names/paths in a unit test against your builder service. Reserve one functional test for the rendered block — asserting list markup in every controller test is churn for no signal.
app/Providers, no Blade::directive, no config/breadcrumbs.php. Budget a rewrite, not an adaptation.default, render main, get an empty nav with no error. Hard-code the name via a constant and you'll never debug this twice.{slug}, entity lookups return null and you get a crumb labeled with an empty string. Build in the controller or in a listener bound to kernel.controller (after argument resolution), not kernel.request.['path' => 'product_list'] throws RouteNotFoundException when Twig renders, not when you add the item — the failure is far from the cause. Pass params for any route with required placeholders.'nav.products') if the app is multilingual; baking "Products" into the manager makes the trail the one untranslated surface on the page.NOASSERTION license — read the license before shipping, pin the version in composer.json, and skim the 2025-05-22 release notes. Because you own the template override, a markup change upstream can't surprise production, but a method signature change can. Wrap the manager in your builder service so an API change is a one-file fix.Home) and the template (override for markup). Avoid subclassing BreadcrumbItem or the manager's internals; neither is a stability contract at this maturity level.bin/console debug:container BreadcrumbManager to confirm the service exists and is public/injectable, debug:twig for the function name, and a temporary dump($breadcrumbs->getItems()) in the controller to see whether the problem is building or rendering. Nine times out of ten it's the second one.How can I help you explore Laravel packages today?