How the Omarchy Plugin System Manages Bar Widgets: Architecture & Registration Flow
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 —
PluginRegistrycreates QML components and callsBarWidgetRegistry.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 that declares its capabilities. The manifest specifies "bar-widget" as a kind and points to a QML entry point.
Consider the weather panel implementation:
// 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 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:
// shell/shell.qml
property BarWidgetRegistry barWidgetRegistry: BarWidgetRegistry { }
PluginRegistry: Loading and Registration
The shell/services/PluginRegistry.qml handles manifest discovery and component instantiation. During shell startup, it:
- Walks plugin directories (
$HOME/.config/omarchy/pluginsplus built-in plugins) - Parses each
manifest.json - For manifests containing
"bar-widget"in theirkindsarray, creates the component and registers it:
// 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 reveals the standard API:
// 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:
// 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— Central catalogue managing widget registration, deregistration, and change notificationsshell/services/PluginRegistry.qml— Discovers plugins, parses manifests, and registers bar widgets with the registryshell/shell.qml— InstantiatesBarWidgetRegistryas a singleton and wires itschangedsignal to the bar UIshell/plugins/panels/weather/BarWidget.qml— Reference implementation showing theBarWidgetcontract- [
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 andbarWidgetentry point - Registry-based:
BarWidgetRegistrymaintains a reactive map of all available widgets, enabling hot-reloading - Decoupled loading:
PluginRegistryhandles discovery and instantiation separately from UI rendering - Standardized API: Widgets implement
injectPanel(),open(),close(), andopenedfor shell integration - Metadata-controlled:
defaultSectionandorderfields 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).
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 →