# How the Omarchy Plugin System Works: MenuModel Architecture Explained

> Explore the Omarchy plugin system and its MenuModel architecture. Learn how plugins inject menu entries without mutating global state using swapProviderRows.

- Repository: [Omacom/omarchy](https://github.com/omacom/omarchy)
- Tags: internals
- Published: 2026-09-05

---

**The Omarchy plugin system uses a lightweight JavaScript module pattern centered around MenuModel in [`shell/plugins/menu/MenuModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/menu/MenuModel.js), where plugins export data models and use `swapProviderRows()` to inject or replace menu entries without mutating global state.**

Omarchy's extensibility is built around a lightweight plugin pattern that lives in the `shell/plugins/` tree. Each plugin is a plain JavaScript module that exports a model describing the data it contributes, such as panel widgets, services, or menu items. According to the omacom/omarchy source code, this architecture keeps the core thin while enabling deterministic ordering and reactive UI updates through Quickshell's runtime.

## Core Architecture of the Omarchy Plugin System

The system revolves around the **MenuModel** class, which acts as the central registry for all menu-related data. Unlike monolithic plugin systems, Omarchy employs a functional approach where plugins provide data structures rather than executing side effects directly.

### The MenuModel Foundation

At the heart of the system sits [`shell/plugins/menu/MenuModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/menu/MenuModel.js). This file parses static JSONC menu definitions and merges them with user-provided overrides. The model maintains two critical collections: **`items`** (the data objects) and **`itemOrder`** (the display sequence). Because these are plain JavaScript objects, the QML UI automatically reacts to updates without requiring manual refreshes.

### The Registration Contract

Every plugin adheres to a simple contract: they expose a **`rows`** array containing objects with `id`, `label`, `icon`, and optional `providerMenu` fields. When loaded by the Quickshell runtime, plugins invoke core helper functions to publish their rows into the global state. This design avoids in-place mutations that Quickshell's engine might lose track of, ensuring type safety and predictable behavior.

## Key Helper Functions in MenuModel.js

The MenuModel exposes two primary functions that plugins use to integrate with the system. These functions handle the complex logic of merging and updating while preserving existing state.

### swapProviderRows for Dynamic Updates

The **`swapProviderRows(items, itemOrder, menuId, rows)`** function, implemented in lines **136‑165** of [`shell/plugins/menu/MenuModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/menu/MenuModel.js), replaces rows for a specific provider while leaving all other menu entries untouched. This enables hot-reloading scenarios where enabling a plugin immediately updates the menu without losing static entries or user customizations.

The function signature accepts the current state (`items` and `itemOrder`), a `menuId` identifying the provider, and the new `rows` array. It returns updated collections that the plugin then publishes back to the model.

### mergeMenuSources for Configuration Merging

The **`mergeMenuSources(defaultItems, userItems)`** function, found in lines **66‑95** of [`MenuModel.js`](https://github.com/omacom/omarchy/blob/main/MenuModel.js), combines built-in menu JSONC with user customizations from `~/.config/omarchy/menu.jsonc`. This merge algorithm guarantees a root entry exists and preserves the intended order while allowing users to add aliases or modify existing items.

## Plugin Types in the Omarchy Ecosystem

The Omarchy plugin system supports several categories of extensions, each following the same register-merge-publish flow but serving different UI purposes.

### Panel Widgets

**[`shell/plugins/bar/BarModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/bar/BarModel.js)** demonstrates how bar-panel plugins register widgets for the top bar. These plugins provide tray icons, keyboard layout indicators, and system monitors by exporting rows that the bar component renders as QML items.

### Service Models

Service-oriented plugins like **[`shell/plugins/services/nightlight/NightlightModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/services/nightlight/NightlightModel.js)** expose state and actions for background services. These models typically include toggle actions and status indicators that appear in system menus, using the same `swapProviderRows` mechanism to update the UI when service states change.

### OSD and Notifications

The **[`shell/plugins/osd/OsdModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/osd/OsdModel.js)** and **[`shell/plugins/notifications/NotificationLogic.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/notifications/NotificationLogic.js)** files illustrate non-menu plugins that handle on-screen displays and notification data. While these don't target the menu system directly, they use the same model-export pattern to communicate with the Quickshell runtime.

## Building a Custom Omarchy Plugin

Creating a plugin requires implementing the registration contract and calling the appropriate helpers. Here is a complete example of a panel plugin that contributes menu rows:

```javascript
/* Example: a simple panel plugin (e.g., "example/Model.js") */
import { swapProviderRows } from '../../plugins/menu/MenuModel.js'

// Define rows the plugin contributes
const rows = [
  {
    id: 'example.toggle',
    label: 'Example Toggle',
    kind: 'action',
    action: 'omarchy-toggle-example',
    icon: 'mdi-lightbulb',
    providerMenu: 'example'
  }
]

// Insert or replace the rows for this provider
export function register () {
  const { items, itemOrder } = MenuModel.current()
  const { items: newItems, itemOrder: newOrder } =
    swapProviderRows(items, itemOrder, 'example', rows)

  // Publish the updated model back to the UI
  MenuModel.update(newItems, newOrder)
}

```

Users can customize how plugins appear by overriding options in `~/.config/omarchy/menu.jsonc`:

```json
{
  "items": {
    "power": { "aliases": ["shutdown", "restart"] }
  }
}

```

The `mergeMenuSources` function automatically integrates these user aliases with the plugin's default configuration.

## Summary

- The **Omarchy plugin system** centers on [`shell/plugins/menu/MenuModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/menu/MenuModel.js), which provides the core registration and merging logic.
- Plugins use **`swapProviderRows()`** (lines 136‑165) to dynamically update menu content without affecting other providers.
- User customizations merge with defaults via **`mergeMenuSources()`** (lines 66‑95), supporting JSONC configuration files.
- All plugins follow a **register-merge-publish** flow, exporting JavaScript models that Quickshell evaluates at runtime.
- The architecture supports panel widgets, service models, and OSD components through the same lightweight contract.

## Frequently Asked Questions

### How does Omarchy handle plugin hot-reloading without losing menu state?

Omarchy uses the **`swapProviderRows`** function to replace only the rows belonging to the specific provider being reloaded. This function, defined in [`shell/plugins/menu/MenuModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/menu/MenuModel.js) at lines 136‑165, creates new `items` and `itemOrder` collections rather than mutating existing arrays in place, ensuring Quickshell's reactive engine detects the changes while preserving entries from other plugins.

### What file structure is required for a new Omarchy plugin?

A plugin must reside in the `shell/plugins/` directory as a plain JavaScript module. The file should export a model—typically named `Model` or `<Name>Model`—and implement a `register()` function that calls `swapProviderRows` with appropriately formatted row objects. Panel-specific plugins often follow the `shell/plugins/panels/<name>/Model.js` convention.

### Can users override plugin-provided menu entries?

Yes. Users can create or modify `~/.config/omarchy/menu.jsonc` to add aliases, change labels, or reorder items. The **`mergeMenuSources`** function in [`MenuModel.js`](https://github.com/omacom/omarchy/blob/main/MenuModel.js) (lines 66‑95) automatically merges these user configurations with the default menu definition, giving user settings precedence while maintaining the plugin's core functionality.

### What is the difference between `items` and `itemOrder` in the MenuModel?

The **`items`** object stores the actual data for each menu entry keyed by ID, while **`itemOrder`** is an array defining the display sequence. This separation allows plugins to update metadata in `items` without affecting ordering, or reorder entries via `itemOrder` without touching the underlying data structures. Both collections are plain JavaScript objects that the QML UI observes for changes.