Skip to main content

Menus — overview

A menu in Liberty is what the user sees in the left navigation panel when they open one of your apps. It groups screens, endpoints, dashboards and routes into a tree of folders and leaves.

Three things are worth knowing up front:

FactImplication
There is no separate "app" object in Liberty.An "app" is just a connector that has a menu attached and whose show_in_switcher flag is on. Both conditions are required for the top app switcher to show its tile.
Menus are stored as a flat list of items linked by parent.The tree is assembled by the backend — easier to hand-edit, round-trips cleanly through TOML, drag operations only ever mutate one list.
Every menu key is a connector name ([menus.<connector>]).The connector named crm carries the menu under [menus.crm]; the screen customers on crm is reached from a leaf with target = "customers_get".

The page that manages menus is Settings → Menus.


The Menus page at a glance

Settings · Menuscrmnomajdenomasx1+ Add a menu for a connectorDiscardSave[menus.crm] · 7 items+ Folder+ Item⎘ Clone🔗 Find usagesTREE▾ 🛡 Security📄 Users📄 Roles▾ 💼 Pipeline📄 Customers📊 Deals dashboard📄 Reports↑ ↓ ← → reorder · indent · outdentINSPECTOR · Customersid=customerslabel=Customersparent=pipelinetype=queryconnector=(uses the app's — crm)target=customers_geticon=usersl.fr=Clientsroles=[user, manager]

Three regions:

RegionWhat it carries
Top scope barOne chip per app (a connector that has a menu). Click a chip → its tree loads below. The + Add a menu for a connector button registers a new connector under Menus. Discard / Save on the right commit or revert page-wide edits.
Tree (left column)The selected app's menu tree. Click a row to select it for editing. On hover, action icons appear over the row's right edge — Move up / down, Indent / Outdent, Add child, Delete. A filter input narrows the list.
Inspector (right column)The full editor for the selected item — a generic form over the MenuItem schema. Fields adapt to the selected type (a folder shows fewer fields than a leaf).

What a menu carries

The schema's top-level shape:

FieldNotes
labelThe app's display name. Falls back to the connector name. Shown in the top app switcher.
itemsA flat list of menu items, linked by their parent field.

Each item:

FieldRequiredWhat it does
idyesUnique inside this menu. Referenced by children's parent.
parentnoPick a folder, or leave blank for a top-level item.
labelyesThe sidebar text.
lnoPer-language label overrides — l.fr, l.de, etc.
iconnoA Lucide icon name (shield, users, chart-bar, …).
typenoBlank = folder. Otherwise one of query, endpoint, dashboard, page (see Item types).
connectornoConnector hosting the target. Blank = the app's own connector (the menu key).
targetconditionalRequired on every leaf; ignored on folders.
paramsnoFixed parameters passed to the target when the item opens.
rolesnoRestrict to these roles. Empty = visible whenever the user can run the target.

Folder vs leaf

The type field decides:

SettingBehaviour
type blankThe item is a folder — groups children. No target, no connector, no params. A folder is hidden when none of its descendants are visible (the runtime collapses empty folders).
type setThe item is a leaf — points at a thing the user can open. Must have a target.

Folders can nest indefinitely. Leaves can't have children — they're terminal nodes.


The four leaf kinds

typeOpenstarget isconnector
queryA screen (TableView) — uses the screen wired to this query.A SELECT query name (customers_get).The connector that owns the query. Blank = the app's.
endpointThe HttpRunner — fires an API connector endpoint.An endpoint name.The API connector. Blank = the app's.
dashboardA dashboard (charts + KPIs).A dashboard id (from [dashboards.*]).Must NOT be set (dashboards live in their own flat namespace).
pageA registered frontend route (a custom feature area, e.g. /nomaflow).The route path.Must NOT be set (the target is a route, not a connector resource).

The schema validator enforces:

  • A leaf needs a target — saving fails without one.
  • A dashboard or page leaf with a connector is rejected (misconfiguration).
  • A folder with target / connector / params is rejected (those fields only make sense on leaves).

Save and reload

The Save button validates the whole MenusFile (unique ids, parents exist, no cycles), writes menus.toml and triggers a hot reload. New menus appear in the top switcher immediately — no process restart.

The validation is strict on three things:

CheckWhy
Every parent reference must point at an existing item.A dangling parent would orphan the subtree.
No cycles (a parent chain that loops).An infinite-loop guard.
No duplicate id within the same menu.Children reference parents by id; duplicates break the link.

Cross-menu duplicates are fine — [menus.crm.security] and [menus.nomasx1.security] coexist without conflict.


How a connector becomes an app

A connector becomes visible in the top app switcher only when both conditions are true:

  1. A menu exists — [menus.<connector>] is set up in Settings → Menus.
  2. show_in_switcher = true on the connector — set in Settings → Connectors → <connector> → Settings.

Miss either one and the connector exists but doesn't appear in the switcher. Setting up the menu first and then ticking show_in_switcher is the usual order.

Note: the Connectors page's own Apps / Data sources grouping is based on menu existence alone — connectors with a menu but show_in_switcher = false still show under Apps there. That grouping is an internal Settings-UI affordance; the user-facing top switcher is what show_in_switcher gates.

The next page covers the wiring from both sides.


Permission pruning

When the user opens the app, GET /api/menus returns the tree filtered to what the user can run:

  • Each leaf's underlying permission (sql:<connector>:<target> for query, api:<connector>:<target> for endpoint, see Permissions and roles) is checked.
  • Items the user lacks permission for are dropped.
  • A folder with no surviving children is collapsed away.
  • Items with a roles filter are kept only when the user's roles intersect the list.

A user sees only the parts of the menu they can actually use — no greyed-out items, no clicks on dead-end links.


What you actually do — quick map

GoalRead
Make a connector show up as an app in the top switcher.Make a connector an app.
Build the tree — folders, leaves, drag/reorder, indent.Build the tree.
Pick the right leaf type (screen / endpoint / dashboard / route).Item types.
Restrict items to specific roles.Permissions and roles.
Add icons + per-language labels.Translations and icons.

What's next