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,
}) %}

Stored nodes leave linked elements unloaded by default. Dynamic projections load their source elements to build their links, but the tree’s element key is null for both stored and projected nodes unless you enable the withLinkedElements output option. This option loads stored nodes’ linked elements after the underlying node fetch, which can still use the tree cache. Setting withLinkedElements in the query criteria instead bypasses that cache for the node fetch.

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.