How to Define and Structure Menus in Omarchy

Omarchy uses declarative JSONC configuration files with dotted identifiers to define hierarchical menu structures that the Qt Quick (QML) plugin renders as a dmenu-style picker at runtime.

Omarchy is a declarative desktop environment by Basecamp that replaces traditional application menus with a data-driven launcher system. Instead of hard-coding UI elements, you define and structure menus in Omarchy by editing JSONC files that the shell dynamically loads and merges at startup. This architecture separates presentation logic from menu content, allowing deep customization without modifying core source code.

Understanding the Menu Architecture

Omarchy's menu system is split between a default definition shipped with the shell and optional user extensions. The system relies on a flat key structure where dotted identifiers infer nesting levels, creating submenus automatically without deeply nested JSON objects.

Core Configuration Files

According to the source code in shell/plugins/menu/Menu.qml, the loader reads from two primary locations:

  • Default menu: $OMARCHY_PATH/default/omarchy/omarchy-menu.jsonc contains the base installation's menu tree.
  • User extensions: $HOME/.config/omarchy/extensions/omarchy-menu.jsonc overrides or appends to the default entries.

At startup, the QML plugin first parses the default file, then merges the user extension if present, with user values taking precedence. This merge strategy ensures system updates do not overwrite personal customizations.

Hierarchical Identifiers

Menu hierarchy is inferred from dot-separated keys rather than nested JSON objects. For example:

  • learn creates a top-level "Learn" category.
  • learn.keybindings places an item inside the Learn submenu.
  • trigger.share.file nests three levels deep: Trigger > Share > File.

This flat structure keeps the JSONC readable while supporting unlimited nesting depth, as the parser splits each key on periods to build the tree model.

Structuring Menu Entries

Each menu entry is a JSONC object requiring three mandatory fields that the picker UI uses for display and execution.

Required Fields

Every entry must specify:

  • icon: A Unicode character or font glyph displayed beside the label (e.g., "").
  • label: The human-readable text shown in the picker.
  • action: The shell command executed when selected (e.g., omarchy-menu-keybindings or omarchy-launch-firefox).

Optional Conditions and Aliases

Advanced entries support:

  • aliases: An array of alternative strings for fuzzy matching.
  • when: A shell predicate that determines visibility; the entry appears only when the command returns exit code 0.

JSONC Syntax Benefits

The format accepts trailing commas and JavaScript-style comments, reducing syntax errors during iterative editing. This is particularly useful when extending ~/.config/omarchy/extensions/omarchy-menu.jsonc, as you can annotate sections without breaking the parser.

Practical Examples

Extending the Default Menu

Add a custom "Utilities" section with a system cleaner tool:

{
  // Top-level category container
  "utilities": {
    "icon": "🧹",
    "label": "Utilities",
    "action": ""
  },
  // Nested item automatically placed under Utilities
  "utilities.cleaner": {
    "icon": "🗑️",
    "label": "System Cleaner",
    "action": "omarchy-cleaner",
    "when": "command -v omarchy-cleaner"
  }
}

Save this to ~/.config/omarchy/extensions/omarchy-menu.jsonc. The shell hot-reloads the file on save, or you can restart the session to force a refresh.

Creating a Conditional Entry

Show a VPN toggle only when the network manager is available:

{
  "network.vpn": {
    "icon": "🔒",
    "label": "Toggle VPN",
    "action": "nmcli con up vpn",
    "when": "nmcli --version"
  }
}

Using CLI Wrappers

The command-line wrappers are thin shims that forward arguments to the QML plugin, ensuring menu definitions remain independent of invocation method:


# Launch the main menu toggle

bin/omarchy-menu toggle

# Display a dynamic picker with specific options

omarchy-menu-select "Pick an app" "firefox" "kitty" "code"

# Pipe input for ad-hoc menus

echo -e "Option 1\nOption 2" | omarchy-menu-input

Runtime Loading and UI Rendering

The menu UI is decoupled from its data source through the QML plugin layer.

QML Plugin Implementation

The shell/plugins/menu/Menu.qml component handles parsing. It reads the JSONC files, evaluates when predicates in a subshell, and constructs the internal tree model passed to the dmenu-style renderer. This implementation ensures the UI updates immediately when the underlying JSONC changes.

Merging Logic

The loader applies a shallow merge strategy:

  1. Load default/omarchy/omarchy-menu.jsonc into the base tree.
  2. If ~/.config/omarchy/extensions/omarchy-menu.jsonc exists, overlay its keys onto the base.
  3. Evaluate all when predicates and prune invisible entries.
  4. Flatten dotted keys into the nested model for the UI delegate.

Summary

  • Omarchy menus are defined in JSONC files located at default/omarchy/omarchy-menu.jsonc (system) and ~/.config/omarchy/extensions/omarchy-menu.jsonc (user).
  • Dotted identifiers like category.subcategory.item create the hierarchy automatically without nested objects.
  • Each entry requires icon, label, and action fields, with optional aliases and conditional when predicates.
  • The shell/plugins/menu/Menu.qml plugin merges configurations, evaluates conditions, and renders the UI.
  • CLI tools such as bin/omarchy-menu and omarchy-menu-select provide shell access without hard-coding menu content.

Frequently Asked Questions

What file format does Omarchy use for menu definitions?

Omarchy uses JSONC (JSON with Comments), which supports trailing commas and JavaScript-style comments. The parser allows more flexible editing than strict JSON, making it ideal for user modifications in ~/.config/omarchy/extensions/omarchy-menu.jsonc.

How do I create a nested submenu in Omarchy?

Use dot notation in your key names. A key named productivity.writing.tools automatically creates a "Productivity" top-level menu containing a "Writing" submenu, which contains the "Tools" item. You do not need to nest JSON objects; the parser infers depth from the key string.

Can I show or hide menu items based on system state?

Yes. Add a when field containing a shell command. The entry appears only if the command returns exit code 0. For example, "when": "which docker" shows the item only when Docker is installed on the system.

Where should I place custom menu definitions?

Place user-specific overrides in ~/.config/omarchy/extensions/omarchy-menu.jsonc. This file is merged with the default menu at default/omarchy/omarchy-menu.jsonc during shell initialization, with user entries taking precedence. Never edit the system default file directly, as changes will be lost on updates.

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 →