# How to Use Providers for Dynamic Menu Content in Omarchy: A Complete Guide

> Learn how to use Omarchy providers to generate dynamic menu content at runtime. Populate submenus with Bash commands or QML logic from JSONC files.

- Repository: [37signals/omarchy](https://github.com/basecamp/omarchy)
- Tags: how-to-guide
- Published: 2026-08-26

---

**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`](https://github.com/basecamp/omarchy/blob/main/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`](https://github.com/basecamp/omarchy/blob/main/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 (`yes` or `no`) 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:

```qml
// 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:

```qml
// 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:

```jsonc
// 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:

```bash
#!/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:

```bash
omarchy menu summon system.fonts

```

## Merging Dynamic and Static Content

The `swapProviderRows` function in [`shell/plugins/menu/MenuModel.js`](https://github.com/basecamp/omarchy/blob/main/shell/plugins/menu/MenuModel.js) handles the integration between static JSONC definitions and runtime provider results. This function ensures that:

1. Dynamic rows replace only previous provider results
2. Static child entries defined in JSONC remain untouched
3. 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 `swapProviderRows` function in [`MenuModel.js`](https://github.com/basecamp/omarchy/blob/main/MenuModel.js) atomically 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`](https://github.com/basecamp/omarchy/blob/main/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.