Omarchy Menu Provider System: How Dynamic Menu Generation Works

The Omarchy menu provider system is a runtime extension mechanism that generates dynamic submenu items on demand by calling JavaScript provider modules declared in the static JSON-C menu definition.

Menu providers solve a fundamental problem in desktop environments: the gap between static configuration files and live system state. The omacom/omarchy project implements this through a clean separation—the base hierarchy lives in default/omarchy/omarchy-menu.jsonc, while dynamic content is supplied by specialized modules under shell/plugins/menu/providers/.


How the Provider Mechanism Works

The core logic resides in [shell/plugins/menu/MenuModel.js](https://github.com/omacom/omarchy/blob/quattro/shell/plugins/menu/MenuModel.js). When the QML UI (Menu.qml) requests rows for a submenu, the model performs a simple branch:

  • No provider declared: Returns children directly from the static JSON-C structure.
  • Provider declared: Dynamically imports the named module and invokes its provide(menuId, context) function.

The provider returns an array of menu items (or a Promise resolving to one). These rows are merged into the model with an added providerMenu property tracking their origin—critical for search indexing and targeted refreshes.


Declaring a Provider in the Menu Definition

Static entries live in default/omarchy/omarchy-menu.jsonc. To enable dynamic generation, add a "provider" field:

{
  "id": "power",
  "label": "Power",
  "icon": "mdi:power",
  "provider": "power-menu"
}

When users open the Power submenu, MenuModel.js locates shell/plugins/menu/providers/power-menu.js and executes its exported function.


Writing a Custom Provider Module

Provider modules are standard JavaScript files exporting a provide() function. They receive the menu ID and a context object with Quickshell environment access.

Here's a minimal provider implementing reboot and shutdown actions:

// shell/plugins/menu/providers/example-provider.js
export function provide(menuId, ctx) {
  return [
    {
      id: `${menuId}-reboot`,
      kind: "action",
      label: "Reboot",
      icon: "mdi:restart",
      action: "omarchy-reboot"
    },
    {
      id: `${menuId}-shutdown`,
      kind: "action",
      label: "Shutdown",
      icon: "mdi:power-off",
      action: "omarchy-shutdown"
    }
  ];
}

The kind: "action" designation tells the UI these are executable items. Other kinds include "submenu" for nested navigation or "link" for external URLs.


Provider Integration in MenuModel.js

The merging logic in MenuModel.js handles the complexity of combining static and dynamic content:

function loadProviderRows(menuId) {
  const entry = items[menuId];
  if (!entry.provider) return [];

  const providerModule = require(`./providers/${entry.provider}.js`);
  const rows = providerModule.provide(menuId, { /* context */ });
  rows.forEach(row => (row.providerMenu = menuId));
  return rows;
}

Key behaviors:

  • Path resolution: Provider names map directly to filenames in shell/plugins/menu/providers/
  • Metadata injection: Each row receives providerMenu = menuId for lifecycle management
  • Error isolation: A failing provider returns empty rows rather than crashing the menu

Built-in Provider Types

The Omarchy menu provider system ships with several standard implementations:

  • power-menu — Generates sleep, reboot, shutdown, and lock actions based on available system capabilities
  • settings — Discovers installed settings panels and hardware configuration modules
  • app-discovery — Scans $PATH and parses .desktop files to populate application launchers

These providers enable the Install → Applications flow and system-dependent configuration menus without hard-coding every possible option.


Refreshing Dynamic Content

Provider results are cached per session. When system state changes—power profile switches, new applications installed, settings modules added—the UI must update.

Trigger a targeted refresh via CLI:

omarchy-update-menu --menu=power

This command signals MenuModel.js to:

  1. Discard cached rows where providerMenu === "power"
  2. Re-execute the power-menu provider
  3. Emit model change notifications to update the UI

The refresh granularity (per-menu rather than full rebuild) preserves performance for large menu hierarchies.


Search Integration

Provider-generated rows participate fully in Omarchy's menu search. The providerMenu annotation enables:

  • Pruning: When a provider refreshes, only its contributed rows are removed from the search index
  • Re-ranking: Fresh provider data can influence result ordering based on current context
  • Filtering: Search can exclude specific provider sources (e.g., hide applications during system recovery)

Summary

  • Static foundation: default/omarchy/omarchy-menu.jsonc defines the base hierarchy with optional "provider" hooks
  • Dynamic extension: JavaScript modules in shell/plugins/menu/providers/ implement provide(menuId, context) to generate runtime content
  • Seamless integration: MenuModel.js merges static and provider rows, tracks provenance via providerMenu, and handles caching
  • Operational control: The omarchy-update-menu CLI triggers granular refreshes; search indexing respects provider boundaries

Frequently Asked Questions

What file format does Omarchy use for its static menu definition?

Omarchy uses JSON-C (JSON with Comments) for omarchy-menu.jsonc. This format accepts standard JSON plus JavaScript-style line comments and trailing commas, making hand-editing more forgiving than strict JSON.

Can a single menu entry combine static children with a provider?

Yes. When a provider is declared, its returned rows merge with any static children defined in the JSON-C file. The MenuModel.js implementation preserves both sources, allowing hybrid menus where some items are fixed and others are dynamically discovered.

How do I debug a provider that isn't generating expected menu items?

Check three locations: first, verify the "provider" name in omarchy-menu.jsonc matches the filename (minus .js) in shell/plugins/menu/providers/. Second, ensure your provide() function exports correctly and handles the context parameter. Third, inspect Quickshell's JavaScript console—provider exceptions are caught but logged, and console.log() output from providers appears there.

Is the provider system limited to built-in menu types?

No. The provider architecture is fully extensible. Create a new .js file in shell/plugins/menu/providers/, export a provide() function, and reference it by name in any menu entry's "provider" field. The system imposes no restrictions on provider logic—you can query D-Bus, read files, or call external binaries to build menu content.

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 →