You are viewing beta documentation for Navigation 4.x.
Frontend

Headless Trees

craft.navigation.tree() returns a nested array tree — useful for JSON APIs, JavaScript menus, and static site generators.

Basic usage

{% set tree = craft.navigation.tree('mainMenu') %}
{{ tree|json_encode }}

You can pass criteria like a node query:

{% set tree = craft.navigation.tree({ handle: 'mainMenu', level: 1 }) %}

Array shape

Each node array includes keys such as:

KeyDescription
title, url, nodeUriLink data
current, active, hasActiveChildActive flags (resolved for the current request)
targetLink target
childrenNested arrays
elementLinked element array when present — null by default; populated when withLinkedElements is enabled

Example output

A small mainMenu on /products/widget might serialise like this:

[
  {
    "id": 101,
    "title": "Products",
    "url": "https://example.com/products",
    "nodeUri": "products",
    "level": 1,
    "active": true,
    "current": false,
    "hasActiveChild": true,
    "target": "",
    "element": null,
    "children": [
      {
        "id": 102,
        "title": "Widget",
        "url": "https://example.com/products/widget",
        "nodeUri": "products/widget",
        "level": 2,
        "active": true,
        "current": true,
        "hasActiveChild": false,
        "target": "",
        "element": null
      }
    ]
  },
  {
    "id": 103,
    "title": "About",
    "url": "https://example.com/about",
    "nodeUri": "about",
    "level": 1,
    "active": false,
    "current": false,
    "hasActiveChild": false,
    "target": "",
    "element": null
  }
]

Each node also includes Craft element keys from toArray()type, menuId, custom fields, and so on. Omitted here for brevity.

With withLinkedElements: true, element holds the linked entry (or category, etc.) as an array instead of null. Projected Dynamic children include "isProjected": true and a slimmer key set — see Projected Node.

Options

{% set tree = craft.navigation.tree('mainMenu', {
    withLinkedElements: true,
}) %}

Unlike nodes(), linked element hydration is opt-in for tree(). Without withLinkedElements, element is null.

tree() runs through the same read pipeline as nodes().all() — caching and active-state rules apply to the underlying fetch. See Performance & Caching.

Dynamic projection

Projected Dynamic children appear in children with isProjected: true unless you opt out with withProjectedChildren(false) on the underlying query criteria.

For GraphQL headless access, see Query Nodes.