How to Define a Custom Bar Widget in Omarchy
Omarchy’s bar architecture relies on a widget registry pattern where each component extends the BarWidget base class and registers with the singleton BarWidgetRegistry to become visible in the UI.
Creating a custom bar widget in Omarchy requires extending the framework’s QML base classes and hooking into the centralized registry system. The basecamp/omarchy repository provides a standardized plugin architecture that manages widget lifecycle, settings injection, and IPC routing through specific base classes and registration APIs. This guide demonstrates the exact implementation patterns found in the source code.
Understanding the BarWidget Architecture
The Base Class (BarWidget.qml)
Every custom bar widget must extend BarWidget defined in shell/Ui/BarWidget.qml. This base class provides three critical injection properties that the bar supplies at runtime:
bar— The host Bar instance containing geometry, sizing, and positioning data.moduleName— A unique string identifier used by the registry and the settings system.settings— A per-widget configuration object read fromshell.json.
The base class also exposes helper functions like broadcast() and setting() for inter-widget communication and configuration access.
The Widget Registry (BarWidgetRegistry.qml)
The BarWidgetRegistry in shell/services/BarWidgetRegistry.qml maintains the global catalogue of available widgets. It stores a map of { widgetId → { component, metadata } } and emits a changed() signal whenever the catalogue updates. The registry is instantiated once by shell.qml as a singleton property barWidgetRegistry, making it accessible to all plugin components.
Step-by-Step Implementation Guide
Extend BarWidget and Define moduleName
Create a QML file in a plugin directory (e.g., shell/plugins/bar/widgets/MyWidget.qml). Extend BarWidget and assign a unique moduleName that matches the ID you will use in shell.json.
// shell/plugins/bar/widgets/MyWidget.qml
import QtQuick
import qs.Commons
import qs.Ui
BarWidget {
id: root
moduleName: "omarchy.mywidget"
// UI implementation here
}
Implement Panel Methods (Optional)
For widgets with expandable panels, implement the standard API so the bar can route IPC calls like shell.summon and shell.toggle:
open()— Display the panel.close()— Hide the panel.togglePanel()— Switch visibility state.openedproperty — Boolean reflecting current state.
These methods allow the bar’s findPanelWidget logic to control your widget remotely.
Register with BarWidgetRegistry
In your plugin’s entry-point QML (typically Plugin.qml), call barWidgetRegistry.register() inside Component.onCompleted:
Component.onCompleted: {
barWidgetRegistry.register(root.moduleName, root, {
title: "My Widget",
icon: "my-icon"
})
}
The registry stores this mapping and notifies the bar to instantiate your widget in the UI.
Configure in shell.json
Add a layout entry to ~/.config/omarchy/shell.json (or the default shell.json) to define placement and settings:
{
"layout": [
{
"id": "omarchy.mywidget",
"slot": "status",
"settings": { "refreshInterval": 30 }
}
]
}
The id must match your widget’s moduleName, and slot determines the bar position (e.g., "left", "center", "status", "right").
Complete Custom Bar Widget Example
Below is a minimal yet complete implementation following the patterns in shell/plugins/panels/clock/BarWidget.qml.
Widget component:
// File: shell/plugins/bar/widgets/MyWidget.qml
import QtQuick
import qs.Commons
import qs.Ui
BarWidget {
id: root
moduleName: "omarchy.mywidget"
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
text: "MyWidget"
tooltipText: "Click to toggle panel"
onPressed: root.togglePanel()
}
Loader {
id: panelLoader
active: true
source: Qt.resolvedUrl("Panel.qml")
visible: false
onLoaded: {
if (panelLoader.item) {
panelLoader.item.bar = root.bar
panelLoader.item.settings = root.settings
panelLoader.item.hostWidget = root
panelLoader.item.anchorItem = button
}
}
}
readonly property bool opened: panelLoader.item ? panelLoader.item.opened : false
function open() { if (panelLoader.item) panelLoader.item.open() }
function close() { if (panelLoader.item) panelLoader.item.close() }
function togglePanel() { if (panelLoader.item) panelLoader.item.toggle() }
Component.onCompleted: {
barWidgetRegistry.register(root.moduleName, root, { title: "My Widget" })
}
}
Panel component:
// File: shell/plugins/bar/widgets/Panel.qml
import QtQuick
Item {
id: panel
property bool opened: false
Rectangle {
anchors.fill: parent
color: "#202020"
radius: 8
Text {
anchors.centerIn: parent
color: "white"
text: "Hello from MyWidget!"
}
}
function open() { opened = true; visible = true }
function close() { opened = false; visible = false }
function toggle() { opened ? close() : open() }
}
Key Source Files and Reference Implementations
shell/Ui/BarWidget.qml— Base class defining injection properties (bar,moduleName,settings) and helper methods.shell/services/BarWidgetRegistry.qml— Implementsregister(),unregister(),availableIds(), andmetadataFor()APIs.shell/plugins/panels/clock/BarWidget.qml— Production example showing IPC handling, panel management, and settings integration.shell/plugins/panels/weather/BarWidget.qml— Alternative implementation demonstrating different UI patterns for the same architecture.shell/shell.qml— InstantiatesBarWidgetRegistryand wires it into the bar’s rendering logic.
Summary
- Extend
BarWidgetto receive the three injection properties required for bar integration. - Call
barWidgetRegistry.register()in your plugin entry-point to add the widget to the global catalogue. - Implement
open(),close(), andtogglePanel()if your widget needs an expandable panel controlled by IPC. - Match
moduleNamein code with theidfield inshell.jsonfor configuration and layout placement. - Reference
shell/plugins/panels/clock/BarWidget.qmlfor production-ready patterns including panel injection and settings handling.
Frequently Asked Questions
What is the minimum code needed for a custom bar widget?
At minimum, create a QML file extending BarWidget with a unique moduleName property and call barWidgetRegistry.register(root.moduleName, root, {}) inside Component.onCompleted. You can omit panel functionality if you only need a static indicator button.
Where should I place my custom widget files?
Place QML files in a plugin subdirectory such as shell/plugins/bar/widgets/ or any path loaded by the plugin system. The entry-point QML file (often named Plugin.qml) must execute the registration code when loaded by shell.qml.
How does the bar know where to position my widget?
Positioning is controlled by the shell.json layout array. Each entry requires an id matching your widget’s moduleName and a slot field defining the bar region (e.g., "left", "center", "status", "right"). The bar iterates over barWidgetRegistry.availableIds() and places instances according to this configuration.
Can I update widget settings without restarting Omarchy?
Yes. The settings property injected by BarWidget reflects the configuration from shell.json. When you modify the JSON file, the settings object updates automatically, allowing your widget to react to configuration changes through property bindings without requiring a restart.
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 →