Skip to content

Drupal Menus & Navigation

Reference for building visitor-facing navigation in Drupal — the menu system's three layers, placing menus as blocks, access control, programmatic and altered links, and menu caching. Verified against Drupal core 11.3.

Scope note: this is content/site navigation, not the admin Navigation module

This topic covers the visitor-facing menu system (menu_ui, menu_link_content, system.menu.*) — main menu, footer menu, custom menus rendered as blocks in your theme.

It does not cover Drupal 11's core Navigation module (core/modules/navigation). That module is the modern collapsible admin sidebar that replaces Toolbar; it is backend UX built on Layout Builder, it creates its own dedicated admin menus (system.menu.content, system.menu.navigation-user-links), and it shares no configuration with the site-facing menus below. It is also not enabled by core's Standard profile (that still ships toolbar) — it arrives via the Drupal CMS drupal_cms_admin_ui recipe. If you are looking for the admin sidebar, see the Navigation module documentation rather than this topic.

I need to... Guide Summary
Understand how menus, menu links, and static links fit together Menu System Fundamentals A Drupal menu tree merges three sources — the system.menu.{id} config container, editor-created menu_link_content entities, and module-shipped *.links.menu.yml — and only the container and static links deploy via config export; editor links need content staging instead.
Render a menu on the page and control its levels/depth Menu Blocks & Placement Every menu auto-exposes as a system_menu_block:{id} plugin derivative — place it in a region and control the rendered slice with level (start depth), depth (levels shown), and expand_all_items (active-trail vs always-open); the same menu can be placed multiple times with different settings.
Control who can edit menus and hide links a user can't reach Menu Access & Permissions Menu access has two distinct layers — administer menu (all-or-nothing edit access, needs menu_admin_per_menu contrib for per-menu scoping) and automatic per-link route access filtering during tree build; external/pathless links are never access-filtered, so don't rely on menu absence to secure them.
Define menu links in code and alter existing ones Programmatic Menu Links & Alter Hooks Ship structural links as *.links.menu.yml (cached plugin definitions, requires cache rebuild after changes) and reserve menu_link_content entities for editor-managed links; alter your own or another module's links at discovery time with hook_menu_links_discovered_alter(), never by editing the defining module.
Keep menus fast and cache them correctly Menu Caching & Performance The single highest-leverage menu-caching fix is writing menu link URIs as entity:node/N rather than internal:/node/N, so title/alias/publish-state changes invalidate the cached menu; route.menu_active_trails varying the menu per route and user.permissions varying it per role are correct, not bugs — never patch a stale menu with max-age 0.