Skip to main content

Build the tree

Once a connector has a menu attached (see Make a connector an app), the tree editor on the right is where you compose the navigation: folders that group, leaves that open screens / endpoints / dashboards / routes.

The model is a flat list of items linked by parent — but the editor lets you work with it as a tree. Drag operations mutate the underlying flat order; the tree on screen always reflects what's saved.


The toolbar

Above the tree:

ButtonWhat it does
+ FolderAdds a folder at the top level (or as a child of the selected item, depending on the implementation). The folder is the only item kind without a type.
+ ItemAdds a leaf, same scope. The leaf opens by default as a query type — change it in the Inspector.
⎘ CloneDuplicates the selected item (subtree included). Prompts for a new id. Disabled when nothing is selected.
🔗 Find usagesOpens a modal listing every place this menu item is referenced (e.g. another connector's home field). Helps before a rename / delete. Disabled when nothing is selected.

The scope bar above the toolbar carries the page-wide Discard / Save — the same as every Settings page.


The tree rows

Settings · Menus · [menus.crm] · tree🔎 Filter 7 items…▾ 🛡 Security security📄 Users security.users🗑📄 Roles security.roles▾ 💼 Pipeline pipeline📄 Customers pipeline.customers📊 Deals dashboard pipeline.deals_dashboard

Each row carries:

ElementWhat it shows
IndentVisual depth — each level is 16 pixels in.
Chevron▾ for an expanded folder, ▸ for collapsed.
IconThe item's icon (Lucide name). Folders default to a folder icon; leaves default to a file icon.
LabelThe label field.
IdMonospace, muted, after the label. Fades out on hover to make room for the actions.
Per-row actions (hover)Move up (↑), Move down (↓), Indent (→), Outdent (←), Add child (+), Delete (🗑).

Click anywhere on the row to select the item — the Inspector on the right loads its full form.


The per-row actions

The icons that appear on hover, left to right:

ActionWhat it does
↑ Move upSwap with the previous sibling. Disabled when the item is the first child of its parent. Moves the whole subtree together — child links follow.
↓ Move downSwap with the next sibling. Disabled at the end.
→ IndentReparent the item under the previous sibling. The previous sibling becomes the new parent. Disabled when there's no previous sibling.
← OutdentMove the item up one level — its grand-parent becomes the new parent. Disabled when the item is already at the top level.
+ Add childAdd a leaf as a child of this item (works on folders; on leaves it'll convert intent — typically add as next sibling, not child).
🗑 DeleteRemove the item and its whole subtree after a confirm modal. The descendant items are dropped in the same operation.

The icons are intentionally compact and only show on hover — the tree stays scannable in steady state.


Building a tree from scratch

A typical flow for a new app's menu:

Step 1 — Add the top-level folders

Click + Folder to add a folder. The default id is something like folder_1; the Inspector lets you rename it.

Top-level patternExamples
One folder per domain.security, pipeline, reports, admin.
One folder per workflow phase.setup, daily-ops, month-end, audit.
One folder per audience.for-managers, for-operators, for-analysts.

Pick the convention that maps to how your users think — not how the data is shaped.

Step 2 — Add the leaves under each folder

Click on a folder row to select it, then + Item (or the per-row + Add child). The new leaf appears under the folder. Set its type, connector, target in the Inspector — see Item types.

Step 3 — Reorder

Use ↑ / ↓ on each row to put items in the order the user should see. The order in the tree = the order in the rendered sidebar.

Step 4 — Nest where needed

Use → (Indent) to push an item under the previous sibling — useful when a folder grows long and you need a sub-folder. Use ← (Outdent) to pull it back out.

Step 5 — Save

The page-level Save validates the whole menu (unique ids, parents exist, no cycles) and writes menus.toml. The hot reload picks up the changes; the user's next page load shows the new tree.


The Inspector form

Click a row → the Inspector on the right loads a form for the selected item. The fields adapt to the type:

FieldAlways shownNotes
idYesRenaming here updates every child's parent reference automatically. Cross-file references (e.g. another connector's home pointing at this item) need a manual fix — use Find usages first.
parentYesA SearchSelect populated with the valid candidates. The tree's indent/outdent actions mutate this field too.
labelYesThe sidebar text.
l (Translations tab)YesPer-language overrides. See Translations and icons.
iconYesLucide icon name.
typeYesDropdown. Blank = folder. Switching from leaf to folder clears target / connector / params.
connectorOnly for query / endpoint leavesBlank = the app's own connector.
targetOnly for leavesA SearchSelect populated with valid targets for the chosen type + connector.
paramsOnly for leavesFixed parameters passed to the target when it opens.
rolesAlwaysRestrict to these roles. See Permissions and roles.

Edits are live in memory — the tree row updates as you type. Page-level Save is what writes to disk.


Renaming an item

Renaming an id is more involved than other fields because every reference must update:

  1. Children: the tree's parent references update automatically.
  2. Cross-file: a connector's home field, or another menu's item that references this one, doesn't update. Use Find usages (the toolbar button) before renaming to see every place that references the item.

For a heavy rename, the safe pattern is Find usages → Clone → fix references → delete the old one — the new id can take over while every reference is updated.


Cloning an item

The ⎘ Clone toolbar button duplicates the selected item:

  • The whole subtree is cloned (a folder with children clones every descendant).
  • The new ids are prompted from a modal — the default is the old id with a suffix.
  • Cross-file references are not rewritten — the clones are brand-new items nobody references yet.

Useful when:

  • You're building a variant of a section (e.g. reports for managers, reports_admin for admins).
  • The same shape applies to two apps and you copy-pasted the tree.
  • You want a sandbox version to experiment with.

Deleting an item

The 🗑 icon (per-row) or the keyboard Delete key on a selected row opens a confirm modal: Delete '<name>' and its descendants?. Confirming removes the item plus every child, grandchild, etc.

There's no soft delete — the item disappears immediately from memory. The page-level Save is what persists. Until you Save, you can Discard to revert.

For cross-file references (a home field pointing at the deleted item), the connector's home becomes invalid — the app falls back to the first visible top-level item on next load. Use Find usages before deleting heavy items.


Filtering long trees

A search box above the tree narrows the visible items by id / label substring. Useful when a menu grows past 30-40 entries.

The filter is case-insensitive and matches anywhere in the id or label. Folders whose children match are kept (with the matching descendants visible).


Common pitfalls

MistakeSymptomFix
Two items with the same id in the same menu.Save validation fails.Pick a unique id; the inspector flags duplicates inline.
parent references an item that doesn't exist.Save validation fails ("unknown parent").Pick a parent from the dropdown — only valid ids are listed.
Parent cycle (A → B → A).Save validation fails ("parent cycle").The tree editor's indent/outdent never creates a cycle; only direct TOML edits do.
Leaf without a target.Save validation fails.Set target in the Inspector.
Folder with target set.Save validation fails ("folder cannot carry target").Either remove the target or set a non-folder type.
dashboard or page leaf with a connector.Save validation fails.These two leaf types don't carry a connector; leave the field blank.
Renaming an item without checking Find usages.Another connector's home or another menu's reference breaks silently.Always check Find usages before renaming.

What's next