# How the Omarchy Plugin System Manages Bar Widgets: Architecture & Registration Flow

> Discover how the Omarchy plugin system manages bar widgets with its three-stage architecture: manifests declare capabilities, PluginRegistry loads components, and BarWidgetRegistry provides a reactive UI catalogue.

- Repository: [37signals/omarchy](https://github.com/basecamp/omarchy)
- Tags: architecture
- Published: 2026-08-18

---

**The Omarchy plugin system manages bar widgets through a three-stage architecture: plugin manifests declare bar-widget capabilities, PluginRegistry loads and instantiates components, and BarWidgetRegistry maintains a reactive catalogue that the UI queries for dynamic rendering.**

Omarchy's desktop shell treats bar widgets as first-class plugins, enabling hot-reloading without requiring a full restart. This article examines the complete lifecycle—from manifest parsing to on-screen rendering—using the actual source code from the `basecamp/omarchy` repository.

## The Bar Widget Lifecycle Overview

Bar widgets in Omarchy follow a strict but flexible registration pattern. Every widget passes through three coordinated systems:

- **Manifest declaration** — defines the plugin kind and entry point
- **Component registration** — `PluginRegistry` creates QML components and calls `BarWidgetRegistry.register()`
- **UI instantiation** — the bar queries registered widgets and renders them by section and order

This design decouples plugin discovery from visual presentation, allowing the shell to add, remove, or reorder widgets reactively.

## Plugin Manifests: Declaring Bar Widgets

Every bar-widget plugin includes a [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) that declares its capabilities. The manifest specifies `"bar-widget"` as a kind and points to a QML entry point.

Consider the weather panel implementation:

```json
// shell/plugins/panels/weather/manifest.json
{
  "id": "weather",
  "name": "Weather",
  "kinds": ["bar-widget", "panel"],
  "entryPoints": {
    "barWidget": "BarWidget.qml",
    "panel": "Panel.qml"
  },
  "metadata": {
    "defaultSection": "right",
    "order": 50
  }
}

```

The `defaultSection` and `order` metadata fields control placement: `left`, `center`, or `right` bar sections, with lower `order` values appearing first within each section.

## BarWidgetRegistry: The Central Widget Catalogue

The [`shell/services/BarWidgetRegistry.qml`](https://github.com/basecamp/omarchy/blob/quattro/shell/services/BarWidgetRegistry.qml) file implements the core registration service. It maintains an internal map of widget IDs to their components and metadata, exposing three primary operations:

| Method | Purpose |
|--------|---------|
| `register(id, component, metadata)` | Adds a widget to the catalogue and increments `revision` |
| `unregister(id)` | Removes a widget and triggers change notification |
| `metadataFor(id)` | Returns placement and ordering information |

The registry emits a `changed` signal whenever its internal state updates. This signal drives reactive UI updates, enabling the bar to respond immediately when plugins are installed, updated, or removed.

The registry is instantiated as a singleton in [`shell/shell.qml`](https://github.com/basecamp/omarchy/blob/quattro/shell/shell.qml):

```qml
// shell/shell.qml
property BarWidgetRegistry barWidgetRegistry: BarWidgetRegistry { }

```

## PluginRegistry: Loading and Registration

The [`shell/services/PluginRegistry.qml`](https://github.com/basecamp/omarchy/blob/quattro/shell/services/PluginRegistry.qml) handles manifest discovery and component instantiation. During shell startup, it:

1. Walks plugin directories (`$HOME/.config/omarchy/plugins` plus built-in plugins)
2. Parses each [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json)
3. For manifests containing `"bar-widget"` in their `kinds` array, creates the component and registers it:

```qml
// Conceptual flow from PluginRegistry.qml
var manifest = JSON.parse(manifestFile.read());
if (manifest.kinds.includes("bar-widget")) {
    var entryPath = pluginDir + "/" + manifest.entryPoints.barWidget;
    var comp = Qt.createComponent(entryPath);
    BarWidgetRegistry.register(manifest.id, comp, manifest.metadata);
}

```

This registration call bridges static plugin description and dynamic UI availability.

## Bar Widget Components: The Contract

Individual bar widgets inherit from a shared base contract. Examining [`shell/plugins/panels/weather/BarWidget.qml`](https://github.com/basecamp/omarchy/blob/quattro/shell/plugins/panels/weather/BarWidget.qml) reveals the standard API:

```qml
// shell/plugins/panels/weather/BarWidget.qml
BarWidget {
    id: root
    
    // Required properties injected by the panel system
    property var bar
    property var settings
    property Item anchorItem
    
    // State exposure for routing
    readonly property bool opened: panelLoader.item?.opened ?? false
    
    function injectPanel(panelComponent) {
        panelLoader.sourceComponent = panelComponent;
        panelLoader.item.bar = root.bar;
        panelLoader.item.settings = root.settings;
        panelLoader.item.anchorItem = root;
    }
    
    function open() {
        if (panelLoader.item) panelLoader.item.open();
    }
    
    function close() {
        if (panelLoader.item) panelLoader.item.close();
    }
    
    // Visual content...
}

```

The `injectPanel()`, `open()`, and `close()` methods enable the shell's `summon/hide/toggle` IPC commands to control panel visibility. The `opened` property allows the routing system to determine which panel currently has focus.

## Rendering: From Registry to Screen

When drawing the bar, the UI queries `BarWidgetRegistry.availableIds()`, sorts by `metadata.defaultSection` and `metadata.order`, then instantiates each component:

```qml
// Conceptual bar rendering logic
Repeater {
    model: BarWidgetRegistry.availableIds().sort((a, b) => {
        var ma = BarWidgetRegistry.metadataFor(a);
        var mb = BarWidgetRegistry.metadataFor(b);
        return ma.order - mb.order;
    })
    
    delegate: Loader {
        sourceComponent: BarWidgetRegistry.componentFor(modelData)
        onLoaded: {
            // Inject bar context and connect signals
        }
    }
}

```

Because `BarWidgetRegistry` stores `Component` objects rather than instances, the bar can create and destroy widget instances on-the-fly. This enables true hot-reloading: updating a plugin's QML file causes the registry to reload the component and emit `changed`, triggering immediate UI reconstruction.

## Key Files and Their Roles

- **[`shell/services/BarWidgetRegistry.qml`](https://github.com/basecamp/omarchy/blob/quattro/shell/services/BarWidgetRegistry.qml)** — Central catalogue managing widget registration, deregistration, and change notifications
- **[`shell/services/PluginRegistry.qml`](https://github.com/basecamp/omarchy/blob/quattro/shell/services/PluginRegistry.qml)** — Discovers plugins, parses manifests, and registers bar widgets with the registry
- **[`shell/shell.qml`](https://github.com/basecamp/omarchy/blob/quattro/shell/shell.qml)** — Instantiates `BarWidgetRegistry` as a singleton and wires its `changed` signal to the bar UI
- **[`shell/plugins/panels/weather/BarWidget.qml`](https://github.com/basecamp/omarchy/blob/quattro/shell/plugins/panels/weather/BarWidget.qml)** — Reference implementation showing the `BarWidget` contract
- **[[`shell/plugins/panels/weather/manifest.json`](https://github.com/basecamp/omarchy/blob/main/shell/plugins/panels/weather/manifest.json)](https://github.com/basecamp/omarchy/blob/quattro/shell/plugins/panels/weather/manifest.json)** — Example manifest declaring bar-widget capability and metadata

## Summary

- **Manifest-driven**: Plugins declare bar-widget status via `"bar-widget"` kind and `barWidget` entry point
- **Registry-based**: `BarWidgetRegistry` maintains a reactive map of all available widgets, enabling hot-reloading
- **Decoupled loading**: `PluginRegistry` handles discovery and instantiation separately from UI rendering
- **Standardized API**: Widgets implement `injectPanel()`, `open()`, `close()`, and `opened` for shell integration
- **Metadata-controlled**: `defaultSection` and `order` fields determine visual placement without code changes

## Frequently Asked Questions

### How does Omarchy achieve hot-reloading of bar widgets without restarting?

The `BarWidgetRegistry` stores `Component` objects and emits a `changed` signal on any registration or unregistration. The bar UI binds to this signal and reconstructs its widget list dynamically. When `PluginRegistry` detects a file change, it reloads the component and calls `register()` again, triggering immediate UI updates.

### Can a single plugin provide multiple bar widgets?

No, the current architecture assumes one bar widget per plugin manifest. A plugin can expose multiple `kinds` (such as both `"bar-widget"` and `"panel"`), but only one `barWidget` entry point. To add multiple distinct widgets, create separate plugin directories each with their own manifest.

### What happens if two plugins specify the same `order` value?

The sorting algorithm in the bar UI uses JavaScript's `sort()`, which is stable. If `order` values match, plugins maintain their relative positions based on `availableIds()` return order, typically filesystem or registry insertion sequence. For predictable positioning, assign unique `order` values spanning a suitable range (e.g., 10, 20, 30).