Skip to content

Core Breadcrumb Architecture

When to Use

Understanding this section is required before writing any custom builder or alter hook. The BreadcrumbManager is the chain dispatcher; every breadcrumb request routes through it.

Decision

Component Role Key method
BreadcrumbBuilderInterface Contract all builders implement applies(), build()
BreadcrumbManager Chain dispatcher, sorted by priority build() calls getSortedBuilders(), iterates, takes first applies() === TRUE
Breadcrumb Value object: links + cache metadata addLink(), addCacheContexts(), addCacheableDependency()
BreadcrumbPreprocess Template preprocessor Converts Link objects to [text, url] arrays for Twig
hook_system_breadcrumb_alter Post-build hook Fires after winning builder; receives Breadcrumb, RouteMatchInterface, $context

Pattern

How BreadcrumbManager::build() resolves the winning builder:

// BreadcrumbManager::build() — simplified
$cacheable_metadata = new CacheableMetadata();
$breadcrumb = new Breadcrumb();
foreach ($this->getSortedBuilders() as $builder) {
  if (!$builder->applies($route_match, $cacheable_metadata)) {
    continue;
  }
  $breadcrumb = $builder->build($route_match);
  $context['builder'] = $builder;
  break;
}
$breadcrumb->addCacheableDependency($cacheable_metadata); // merge applies() metadata
$this->moduleHandler->alter('system_breadcrumb', $breadcrumb, $route_match, $context);
return $breadcrumb;

The Breadcrumb class uses RefinableCacheableDependencyTrait, implementing RefinableCacheableDependencyInterface. Any cacheability added in applies() is automatically merged into the returned breadcrumb — so do not duplicate it in build().

Service registration: builders are tagged with breadcrumb_builder in *.services.yml. The RegisterBreadcrumbBuilderPass compiler pass collects them by priority and calls BreadcrumbManager::addBuilder().

Priority order: higher number = higher priority = checked first. The first builder where applies() returns TRUE wins; subsequent builders are never called.

Common Mistakes

  • Adding cache metadata in both applies() and build() — it is merged automatically; double-adding causes no error but is redundant
  • Returning NULL from build() — the manager throws UnexpectedValueException; always return a Breadcrumb instance
  • Assuming all builders run — only the winning builder's build() is called; losers only have applies() called

See Also