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.file is a child of trigger.share)
  • Kind inference determines entry type:
    • action field present → leaf that runs a shell command
    • target field 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

  1. Default items are read first from default/omarchy/omarchy-menu.jsonc
  2. User items from the extensions file are overlaid
  3. Existing IDs: Only declared fields replace defaults; original position is retained
  4. New IDs: Appended to the menu structure
  5. Synthetic root: Injected if missing (mergeMenuSources ensures root exists)

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 = checked
  • d = disabled

Results are cached and consulted by isVisible, isDisabled, and labelFor to hide rows, display check-marks, or dim entries.

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:

  1. Exact matches
  2. Prefix matches
  3. Description matches
  4. 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →