How to Use Providers for Dynamic Menu Content in Omarchy: A Complete Guide
Omarchy generates dynamic menu entries at runtime using provider functions defined in shell/plugins/menu/Menu.qml, which return tab-delimited rows from Bash commands or QML-native logic to populate submenus declared in JSONC configuration files.
Omarchy's menu system combines static JSONC definitions with runtime providers to create flexible, context-aware interfaces. When you need menus that reflect current system state—like installed fonts, active applications, or network interfaces—you implement providers for dynamic menu content in Omarchy instead of hard-coding entries. The architecture separates presentation logic in configuration files from data retrieval mechanisms implemented in the QML plugin layer.
Understanding the Omarchy Menu Provider Architecture
The menu system distinguishes between static entries and dynamic content. Static items are defined in default/omarchy/omarchy-menu.jsonc (and user-specific overrides at ~/.config/omarchy/extensions/omarchy-menu.jsonc), while dynamic rows are generated at runtime when a submenu includes a provider field.
Static vs. Dynamic Menu Entries
According to the Omarchy source code in docs/menu.md, when a submenu entry includes the provider property, the menu system ignores static row definitions and instead queries the registered provider. The runtime merger occurs through swapProviderRows in shell/plugins/menu/MenuModel.js, which atomically replaces previous dynamic rows without disturbing static child entries.
The Provider Contract
Every provider must return data using a strict tab-delimited format:
label<TAB>value<TAB>current
- label: The visible text displayed in the menu
- value: The underlying identifier passed to action handlers
- current: A boolean flag (
yesorno) indicating whether the row displays a checkmark (✓)
Rows automatically receive IDs derived from the submenu's ID concatenated with a slugified version of the value. If ID collisions occur, the system appends a hyphen to ensure uniqueness.
Configuring Dynamic Menu Providers in Menu.qml
Providers are declared in the providers map inside shell/plugins/menu/Menu.qml. Each provider specifies a command to execute, an icon, an optional actionFor function to handle selection, and a volatile flag for runtime refresh behavior.
Bash One-Liner Providers
Most providers execute Bash commands that print the tab-delimited contract to stdout. The fonts provider demonstrates a typical implementation:
// shell/plugins/menu/Menu.qml
providers: {
"fonts": {
command: "omarchy list-fonts",
icon: "fa-solid fa-font",
actionFor: (value) => `omarchy set-font ${value}`,
volatile: true
}
}
The volatile flag triggers re-execution every time the submenu opens. This ensures the menu reflects current system state—essential for data that changes while the shell runs, such as newly installed packages or removable media.
QML-Native Providers
Some providers bypass shell execution and use QML-native functions for better performance. The built-in apps provider accesses the AppLibrary directly without spawning a subprocess:
// shell/plugins/menu/Menu.qml
"apps": {
icon: "fa-solid fa-box",
actionFor: (desktopId) => `omarchy launch ${desktopId}`
}
Defining Provider-Powered Submenus in JSONC
Reference your provider in the menu configuration using the provider key:
// default/omarchy/omarchy-menu.jsonc
{
"system.fonts": {
"label": "Fonts",
"provider": "fonts"
},
"system.apps": {
"label": "Applications",
"provider": "apps"
}
}
When the user opens these submenus, Omarchy invokes the corresponding provider and populates rows dynamically.
Implementing Custom Provider Scripts
Create executable scripts that output the tab-delimited contract. Here is a complete Bash provider that lists available fonts and marks the current default:
#!/bin/bash
# omarchy list-fonts implementation
DEFAULT_FONT=$(gsettings get org.gnome.desktop.interface font-name | tr -d "'")
fc-list : family | sort -u | while read -r font; do
current="no"
if [[ "$font" == "$DEFAULT_FONT" ]]; then
current="yes"
fi
printf "%s\t%s\t%s\n" "$font" "$font" "$current"
done
Invoke the dynamic submenu from the command line:
omarchy menu summon system.fonts
Merging Dynamic and Static Content
The swapProviderRows function in shell/plugins/menu/MenuModel.js handles the integration between static JSONC definitions and runtime provider results. This function ensures that:
- Dynamic rows replace only previous provider results
- Static child entries defined in JSONC remain untouched
- Row IDs remain stable across refreshes to prevent UI flicker
When a volatile provider executes on submenu open, this merging process repeats, updating the visible items while preserving menu structure.
Summary
- Dynamic content in Omarchy menus originates from providers declared in
shell/plugins/menu/Menu.qml, not static JSONC files. - Providers return tab-delimited rows (
label<TAB>value<TAB>current) via Bash commands or QML-native functions. - The volatile flag forces providers to re-run on every submenu open, ensuring real-time accuracy for changing data.
- Row IDs are generated automatically from submenu IDs and slugified values to prevent collisions.
- The
swapProviderRowsfunction inMenuModel.jsatomically merges provider results with static menu entries.
Frequently Asked Questions
How do I add a new dynamic menu source in Omarchy?
Add a new entry to the providers map in shell/plugins/menu/Menu.qml defining the command, icon, and optional actionFor function. Then create a submenu in your JSONC menu file referencing the provider name with provider: "your-name". For Bash providers, ensure your script outputs the tab-delimited contract to stdout.
What is the difference between volatile and static providers?
Volatile providers re-execute their command every time the submenu opens, which is essential for data that changes during the session like installed fonts or network status. Static providers (default behavior) execute once and cache results until the shell restarts. Set volatile: true in the providers map to enable real-time updates.
Why does my provider script need to use tabs as delimiters?
Omarchy's MenuModel.js parses provider output using tab delimiters to distinguish between the visible label, the internal value passed to actions, and the current state flag. Spaces or commas will cause parsing failures. The format strictly requires: label<TAB>value<TAB>current per line.
How are provider-generated menu items identified?
The system generates row IDs by combining the submenu's ID with a slugified version of the row's value field. If collisions occur (identical values producing identical IDs), Omarchy appends a hyphen to maintain uniqueness. This ID generation happens automatically in the menu model layer when processing provider results.
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 →