{warning} Before upgrading to Navigation 4, review the Upgrading from v3 guide for breaking changes and required migration steps.
Breaking Changes
- Menu builder changes are now staged until the menu is saved. Editors must save the menu to publish node additions, deletions, and structure changes.
craft.navigation.getActiveNode()now returns the deepest exact current-page match instead of the first active ancestor.- Custom URLs no longer expand environment variables or Craft aliases. Use relative URLs, literal destinations, or the
{site.baseUrl}token.
Added
- Added a React control panel menu builder with staged build sessions for structure moves, node additions and deletions, and menu content drafts, including publish and discard workflows.
- Added a
MenuCraft element with menu-level custom fields, public menu queries, and dedicated Menu Fields and Menu Content editing interfaces. - Added explicit node type classes and extensible schemas and editors for node creation and editing (#413).
- Added the Group/column structural node type (#416) and
Node::getTag()support for passive and structural nodes (#369). - Added a unified Dynamic node type with extensible entry, category, asset, and Commerce product sources; read-time projected children; source conditions and ordering; optional limits; and pending/preview projections (#413).
- Added per-site node link settings for URLs, suffixes, and linked elements (#405, #360).
- Added menu title translation settings so node title overrides can propagate by site, site group, language, or custom key (#394).
- Added controls for whether propagated nodes are enabled on other sites by default (#400), an auto-enable-new-sites plugin setting (#428), and Feed Me mapping for per-site linked elements (#405).
- Added node copying between sites with per-copy element remapping (#425, #453).
- Added migration support for MenuBuilder, Navkit, and FreeNav menus, including compatible hierarchy, site, element-link, attribute, and dynamic-source data.
- Added the Context API through
craft.navigation.context(handle), including sibling, branch, and ancestor helpers (Discussion #368). - Added menu breadcrumbs through
craft.navigation.menuBreadcrumbs(handle)(#335) and URL-segment breadcrumbs throughcraft.navigation.urlBreadcrumbs(). - Added batched linked-element hydration, in-memory hierarchy wiring, and tree assembly through the
NodeReadservice (#341, #412). - Added tagged front-end navigation caching with configurable modes and profiles, plus
craft.navigation.invalidateCache()andNavigationCache::EVENT_INVALIDATEfor custom cache integrations (#367). - Added node query controls for menu, linked-element, hierarchy, projected-child, and navigation-cache hydration, including automatic hierarchy wiring for navigation-scoped reads (#341, #413).
- Added single-pass active-state resolution,
craft.navigation.getActiveNodes(),craft.navigation.getCurrentNodes(), andcurrentandhasActiveChildtree output (#418). - Added GraphQL menu, navigation context, and menu breadcrumb queries (#325, #335, Discussion #368), plus node hydration arguments and projected-node schema support (#413, #422).
- Added an indicator when an element-linked node has a custom title and no longer follows its linked element’s title.
- Added Pest integration and coverage for build sessions, builder state, menu content, Dynamic node schemas, linked-element lifecycles, and active-state behavior (#413).
- Added documentation for node query cache and hydration controls, active-state helpers, GraphQL site and language behavior, linked-element lifecycles, and cache invalidation (#426).
Changed
- Navigation now requires Craft CMS 5.9.11 or later.
- Renamed the
Navsservice and related APIs, database tables and columns, project config paths, permissions, GraphQL schema components, control panel routes, templates, events, and internal settings from “nav” to “menu”; deprecated compatibility shims and redirects remain where documented. - Node
typevalues and menu permission settings now use Navigation node type class names and are migrated automatically. - Dynamic nodes are removed when their source section, category group, volume, or product type is deleted (#385).
- Linked-element lifecycle handling now uses node type classes and per-site linked elements (#386); soft deletion disables nodes while preserving their previous state, restoration re-enables them, and hard deletion removes them.
- Linked entry title synchronization now respects per-site node title overrides (#230, #394).
- Active-state matching now includes per-site URL suffixes and skips control panel, console, and preview requests (#360, #384).
- Improved active-state matching for entry-backed nodes on sites with path-based URLs (#408), site nodes on descendant pages (#435), and duplicate URL matches by returning the deepest current-page match (#418).
- Improved
node.hasActiveChild(),craft.navigation.render(), andcraft.navigation.tree()to use resolved in-memory hierarchy and active-state data instead of recursive structure queries. - Improved cache invalidation for menu and node changes; active and ancestor state is applied after cache hits rather than stored in cached payloads.
- Dynamic projections now default to Craft’s live/public statuses, with non-live sources available only through explicit pending or preview reads.
- Author-supplied URL, class, and custom-attribute tokens now render in Craft’s sandboxed Twig environment with bounded site context. Custom URLs no longer expand environment variables or aliases; use a relative URL, literal destination, or the sandboxed
{site.baseUrl}token. - Passive and group nodes can render as
<span>rather than<a>throughgetTag(); custom templates that assume every node is an anchor must be updated (#369). - Restructured the plugin documentation into Menus, Templates, Frontend, GraphQL, Reference, and Integrations sections.
Fixed
- Fixed cross-site node copies losing their hierarchy or returning empty
node.children, including cached level-scoped queries (#452). - Fixed an error when restoring a soft-deleted menu through project config (#415).
- Fixed N+1 queries when reading
node.childrenwithout pre-wired hierarchy data (#341), readingnode.elementwithout linked-element hydration, and propagating ancestor active states (#412). - Fixed per-site custom URLs and link settings being stored against shared node data instead of the corresponding site (#405, #360).
Deprecated
- Deprecated
craft.navigation.nav(),getNavByHandle(),getNavById(),getAllNavs(),breadcrumbs(), andgetRegisteredElements()in favor of the corresponding menu, URL breadcrumb, and node type APIs. - Deprecated
Node::getNav(),getIsActive(), andsetIsActive()in favor ofgetMenu(),getActive(), andgetActiveState(). - Deprecated
Menu::getNav()in favor of the Menu element API orgetMenuHandle(). - Deprecated
NodeQuery::nav(),navHandle(),navId(), andelementSiteId()in favor ofmenu(),menuHandle()/handle(),menuId(), and per-site link settings. - Deprecated the
navHandle,nav, andnavIdnode query criteria keys in favor ofhandleandmenuId. - Deprecated the
nav,navHandle, andnavIdGraphQL node query arguments in favor ofmenuHandleandmenuId, and thenavId,navHandle, andnavNamefields in favor of theirmenu*equivalents. - Deprecated the
Navsplugin API,PluginTrait::getNavs(),NavEvent,EVENT_*_NAVevents, and$event->navin favor of theirMenuequivalents. - Deprecated the
Elementsservice andRegisterElementEventin favor ofNodeTypesandRegisterNodeTypeEventwithElementNodeTypesubclasses. - Deprecated the
builderLiveStructureplugin setting; structure changes are staged through build sessions by default.
Removed
- Removed the
disabledElementsandpropagateSiteElementsplugin settings. - Removed the legacy
_types/*/modal.htmlTwig templates for Site, Custom URL, and Dynamic nodes; slide-out fields now render through PHP helpers (#413).