How Menus Are Served
What happens between your stored rules and the visitor's navbar, on every client area page.
Hook flow
- Late-priority (1000) handlers catch
ClientAreaPrimaryNavbarandClientAreaSecondaryNavbar, so core and other addons build the tree first. - The visitor's audience (Visitors vs Clients) is detected from the session; the matching rows load from
storage/menus.json. - Each row applies top-down: rename, retarget, reorder, remove, custom-add. A row whose parent vanished takes its whole subtree out silently.
Label priority
For every item the engine picks the first available text:
- The visitor language's explicit translation (e.g. the Arabic Label).
- Your customized label — but only if you actually renamed the item.
- Nothing — the live node keeps WHMCS's own language-file text (e.g. Arabic from
lang/arabic.php).
This is why untouched items follow WHMCS translations automatically, in every language, with zero work.
Storage
storage/menus.json— menu rows (labels, translations, icons, badges, clicks). Locked, atomic writes; corrupt files are quarantined aside.hsc_mm_settings— settings key/value rows.hsc_mm_log— activity entries (pruned by cron).
Clicks, sticky & colors
- Clicks post to the client area controller (
index.php?m=menuManager&action=click) via beacon and increment the row counter. - Sticky, colors and badge pills render as page CSS/JS, gated to viewports of 992px and up.
Safe by default — with no stored rows the engine passes menus through untouched. Every lookup is guarded; unknown items are skipped, never fatal — the client area always renders.