Omarchy Menu System Architecture: How the Quickshell-Based UI Works
Omarchy's menu system is a three-layer, data-driven architecture built on Quickshell that uses JSONC configuration files, a JavaScript runtime model, and dynamic providers to render a hot-reloadable, extensible desktop menu.
The Omarchy menu system, as implemented in basecamp/omarchy, separates static definitions from runtime behavior through a carefully layered design. This architecture enables users to customize their menu without modifying core files or restarting the shell, while maintaining consistent performance through batched guard evaluation and efficient search ranking.
Three-Layer Architecture
The Omarchy menu architecture consists of tightly coupled layers that handle configuration, extension, and rendering:
| Layer | Responsibility | Primary Source |
|---|---|---|
| Static definition | JSONC files describing every menu entry (icons, labels, actions, links, providers, aliases, guards) | default/omarchy/omarchy-menu.jsonc |
| User extensions | Optional overlay that can retitle, re-icon, or add new items without restarting | ~/.config/omarchy/extensions/omarchy-menu.jsonc (sample in config/omarchy/extensions/omarchy-menu.jsonc) |
| Runtime engine | JavaScript model that parses JSONC, merges sources, evaluates guards, resolves routes, and drives the QML UI | shell/plugins/menu/MenuModel.js |
JSONC Data Format
The Omarchy menu system uses JSONC (JSON with Comments) as its configuration language. The parser strips comments via stripJsonc before passing to JSON.parse.
Key Format Characteristics
- Line comments (
//) and trailing commas are permitted - Dotted IDs implicitly define the tree hierarchy (e.g.,
trigger.share.fileis a child oftrigger.share) - Kind inference determines entry type:
actionfield present → leaf that runs a shell commandtargetfield present → link to another submenu- Neither → submenu container
The complete Entry schema is documented in docs/menu.md, covering fields like icon, label, title, action, target, provider, aliases, description, and guard expressions (when, checked, disabled).
Loading and Merging Process
At startup, the shell reads both JSONC files and watches them for changes. The MenuModel.js implements this through three functions: parseMenuJsonc → normalizeItem → mergeMenuSources.
Merge Behavior
- Default items are read first from
default/omarchy/omarchy-menu.jsonc - User items from the extensions file are overlaid
- Existing IDs: Only declared fields replace defaults; original position is retained
- New IDs: Appended to the menu structure
- Synthetic root: Injected if missing (
mergeMenuSourcesensuresrootexists)
Dynamic Providers
Static JSONC entries define fixed rows. Submenus can specify provider: "<name>" to receive runtime-generated content.
Provider names are declared in shell/plugins/menu/Menu.qml within the providers map. When a submenu has a provider, the runtime requests rows from that provider, with each row receiving the submenu's ID as providerMenu.
The swapProviderRows function merges provider output by replacing only rows from that specific provider, leaving static items untouched. This enables live updates to dynamic sections (like plugin lists or window menus) without full re-renders.
Guard Evaluation System
Menu entries can use when, checked, or disabled fields containing Bash expressions that control visibility and state.
Batched Execution
Rather than forking Bash for each row, the Omarchy menu system batches all guard expressions into a single Bash script (guardScript). The script reports results in the format <id>:<w|c|d>:<0|1> for each guard type:
w= when (visibility)c= checkedd= disabled
Results are cached and consulted by isVisible, isDisabled, and labelFor to hide rows, display check-marks, or dim entries.
Routing and Search
The Omarchy menu system implements sophisticated navigation and query matching.
Route Resolution
resolveRoute normalizes input by:
- Converting to lower-case
- Replacing
_with-
The resolver looks up exact IDs first, then falls back to aliases.
Search Ranking
Search tokens are built from label, leaf ID, and aliases (combined as nameSearchText). The matchesQuery and searchScore functions rank results with this priority:
- Exact matches
- Prefix matches
- Description matches
- Depth and original ordering
CLI Integration
The bin/omarchy-menu wrapper forwards commands to the menu plugin's IPC surface, ensuring consistency between graphical and scripted access:
omarchy menu # toggle root menu
omarchy menu toggle system # open/close a specific route
omarchy menu summon style.theme
omarchy menu refresh # re-parse JSONC files
These commands invoke the same runtime logic used by the QML UI.
Practical Configuration Examples
Adding a Custom Entry
Place in ~/.config/omarchy/extensions/omarchy-menu.jsonc:
{
// Add a "Launch Calculator" action under the root menu
"tools.calculator": {
"icon": "",
"label": "Calculator",
"action": "omarchy-launch calculator"
}
}
The dotted ID tools.calculator creates a new tools submenu (if absent) with a calculator leaf. The presence of action infers this as an action entry.
Overriding an Existing Entry
Change the icon of the built-in "System" menu:
{
"system": {
"icon": "⚙️"
}
}
Reusing the ID system overwrites only the icon field while preserving the default action and children.
Defining a Provider-Backed Submenu
In the default menu:
{
"plugins": {
"label": "Plugins",
"provider": "plugins"
}
}
The plugins submenu receives its rows from the provider named plugins declared in Menu.qml.
Using Conditional Visibility
Hide a row when a package is already installed:
{
"install.git": {
"icon": "",
"label": "Install Git",
"action": "omarchy-pkg-add git",
"when": "omarchy-pkg-missing git"
}
}
The row appears only if git is not installed; the guard is evaluated in the batched guard script.
Key Source Files
| File | Role |
|---|---|
default/omarchy/omarchy-menu.jsonc |
Core static menu definition |
config/omarchy/extensions/omarchy-menu.jsonc |
User-level overlay sample |
docs/menu.md |
Complete schema and format specification |
shell/plugins/menu/MenuModel.js |
JavaScript runtime model (parsing, merging, guards, routing) |
shell/plugins/menu/Menu.qml |
QML plugin (UI rendering, providers map) |
bin/omarchy-menu |
CLI wrapper for menu commands |
Summary
- Omarchy menu architecture uses three layers: static JSONC, user extensions, and a JavaScript runtime engine
- JSONC format supports comments, trailing commas, dotted IDs for hierarchy, and kind inference from field presence
- Merging rules overlay user entries onto defaults, with field-level replacement and position preservation
- Dynamic providers enable runtime-generated content through the providers map in
Menu.qml - Guard evaluation batches Bash expressions into a single script for performance, with cached results
- Search and routing normalize input, resolve exact IDs and aliases, and rank by match quality and depth
- CLI wrapper (
bin/omarchy-menu) exposes all functionality for scripting and automation
Frequently Asked Questions
How do I add a custom menu entry without modifying core files?
Create or edit ~/.config/omarchy/extensions/omarchy-menu.jsonc and add entries using dotted IDs. The user extension layer overlays your changes onto the default menu without requiring shell restart. Only declared fields replace defaults; unspecified fields are inherited.
What is the difference between action, target, and plain entries in Omarchy's menu JSONC?
The Omarchy menu system infers entry type from field presence: entries with an action field become executable leaves, entries with target become links to other submenus, and entries with neither become submenu containers. This eliminates the need for explicit type declarations.
How does conditional visibility work in the Omarchy menu?
Entries use when, checked, and disabled fields containing Bash expressions. At runtime, all guards are batched into a single guardScript execution to avoid per-row forks. Results are parsed and cached, then consulted by isVisible, isDisabled, and related functions to control presentation.
Can I trigger the Omarchy menu from scripts or keyboard shortcuts?
Yes. The bin/omarchy-menu CLI provides commands like omarchy menu (toggle root), omarchy menu summon <route>, and omarchy menu refresh. These use the same IPC surface as the graphical UI, ensuring consistent behavior across interaction methods.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →