How to Create a New Bar Widget Plugin for the Omarchy Shell
Create a new bar widget plugin by adding a QML file that inherits from BarWidget, an optional JavaScript model for business logic, and a manifest file that registers the widget with the shell.
The Omarchy shell (from the omacom/omarchy repository) renders its status bar through a Quickshell plugin system where each component is a self-contained QML module. To create a new bar widget plugin, you implement a declarative UI component, bundle it with a JSON manifest, and reference it in your user configuration.
Overview of the Bar Widget Architecture
Omarchy’s status bar is implemented as the omarchy.bar Quickshell plugin. Each widget is a QML component backed by an optional JavaScript model, discovered at runtime through a *.manifest.json file. The loading logic resides in shell/plugins/bar/BarModel.js, which resolves widget IDs from shell.json into filesystem paths and instantiates them as children of the bar container.
The creation process follows four mandatory steps:
- Create a QML widget file (
MyWidget.qml) undershell/plugins/bar/widgets/ - Optionally add a JavaScript model (
MyWidgetModel.js) for non-UI logic - Create a manifest (
MyWidget.manifest.json) declaring the widget’s ID and entry point - Enable the plugin and add it to the bar layout in
~/.config/omarchy/shell.json
Step 1: Create the QML Widget File
Create a new file under shell/plugins/bar/widgets/. The root element must be BarWidget, a base type defined in shell/plugins/bar/Bar.qml that injects required properties including bar, moduleName, and settings.
// shell/plugins/bar/widgets/MyWidget.qml
import QtQuick
import Quickshell
import qs.Commons
import "MyWidgetModel.js" as MyWidgetModel
BarWidget {
id: root
moduleName: "omarchy.my-widget"
property int counter: 0
WidgetButton {
anchors.fill: parent
bar: root.bar
text: "MY"
tooltipText: "Clicked " + root.counter + " times"
onPressed: root.counter++
}
}
Why BarWidget? All first-party widgets inherit from this base class. It provides the standardized interface that BarModel.js expects when constructing the layout tree, ensuring your widget receives the correct shell context and settings object.
Step 2: Add the JavaScript Model (Optional)
If your widget interacts with system state or performs complex data processing, isolate that logic in a sibling JavaScript file. Import it into your QML using the standard QtQuick module import syntax.
// shell/plugins/bar/widgets/MyWidgetModel.js
function formatNumber(n) {
return String(n).padStart(2, "0");
}
module.exports = { formatNumber };
Reference the model in your QML:
import "MyWidgetModel.js" as MyWidgetModel
// Inside a component:
text: MyWidgetModel.formatNumber(root.counter)
This pattern keeps UI declaration separate from business logic, matching the architecture used by built-in widgets like KeyboardLayout (see KeyboardLayoutModel.js for a production example).
Step 3: Register the Widget with a Manifest
Every widget requires a manifest file that declares its kind (always "bar-widget" for bar components) and its entry QML file.
Create MyWidget.manifest.json in the same directory as your QML file:
{
"id": "omarchy.my-widget",
"kind": "bar-widget",
"entry": "MyWidget.qml"
}
Critical constraints:
- The
idmust be globally unique across the Omarchy ecosystem (use reverse-domain notation). - The
entrypath is relative to the manifest’s directory. - Do not edit the bar’s own
manifest.json; the shell discovers your widget automatically when the manifest is present inshell/plugins/bar/widgets/.
Step 4: Enable and Configure the Widget
Enable the plugin using the Omarchy CLI:
omarchy plugin enable omarchy.my-widget
Then define its position in the bar by editing ~/.config/omarchy/shell.json:
{
"version": 1,
"bar": {
"layout": {
"right": [
{ "id": "omarchy.tray" },
{ "id": "omarchy.my-widget" }
]
}
}
}
The bar monitors this configuration file in real time; the widget appears immediately upon saving. Alternatively, use the CLI to automate placement:
omarchy bar move right omarchy.my-widget
How the Bar Loads Custom Widgets
When the shell initializes, BarModel.js reads the bar.layout object and constructs a layout tree. For each entry, the model invokes customModulePath(entry, home, configDir) to resolve the widget’s filesystem location. For first-party widgets with manifests, this resolves to widgets/MyWidget.qml.
Key functions in shell/plugins/bar/BarModel.js governing this behavior:
customModuleType(entry)– Determines whether a layout entry references a command or a QML module.customModulePath(entry, home, configDir)– Resolves the absolute path to the widget’s QML file based on the manifest ID.inlineSettingsDelta(current, next)– Optimizes live configuration updates, allowing the bar to update widget properties without rebuilding the entire layout.
Complete Minimal Widget Example
The following file tree represents a deployable widget:
shell/
└─ plugins/
└─ bar/
└─ widgets/
├─ MyWidget.qml
├─ MyWidget.manifest.json
└─ MyWidgetModel.js
MyWidget.qml:
import QtQuick
import Quickshell
import qs.Commons
import "MyWidgetModel.js" as MyWidgetModel
BarWidget {
id: root
moduleName: "omarchy.my-widget"
property int counter: 0
WidgetButton {
anchors.fill: parent
bar: root.bar
text: "MY"
tooltipText: "Clicked " + root.counter + " times"
onPressed: root.counter = MyWidgetModel.double(root.counter)
}
}
MyWidget.manifest.json:
{
"id": "omarchy.my-widget",
"kind": "bar-widget",
"entry": "MyWidget.qml"
}
MyWidgetModel.js:
function double(n) { return n * 2; }
module.exports = { double };
Install the widget by copying these files to shell/plugins/bar/widgets/, then enable and position it using the commands in Step 4.
Summary
- Inherit from
BarWidgetinshell/plugins/bar/Bar.qmlto receive required shell context and properties. - Isolate logic in an optional JavaScript model imported into your QML.
- Declare the manifest with a unique ID, kind
"bar-widget", and relative entry path. - Enable via CLI with
omarchy plugin enable <id>and position viashell.jsonoromarchy bar move. - Loader resolution happens in
BarModel.jsthroughcustomModulePath(), supporting live reloads without shell restart.
Frequently Asked Questions
What is the required directory structure for a new bar widget?
Place all widget files under shell/plugins/bar/widgets/ within the Omarchy repository or your local configuration directory. The QML file, manifest, and optional JavaScript model must share the same base filename (e.g., Clock.qml, Clock.manifest.json, ClockModel.js) to ensure the loader in BarModel.js resolves dependencies correctly.
Can I update a widgets code without restarting the Omarchy shell?
Yes. The bar configuration is monitored in real time. Changes to ~/.config/omarchy/shell.json apply instantly. However, modifications to the QML or JavaScript source files typically require a shell restart unless the widget explicitly implements hot-reloading logic using Quickshell’s dynamic instantiation APIs.
What is the difference between kind: "bar-widget" and custom command modules?
The "bar-widget" kind indicates a native QML component that inherits from BarWidget and is discovered via a manifest file. Custom command modules (referenced directly in shell.json without a manifest) execute external processes or load arbitrary QML files that do not necessarily inherit from the standard base class, bypassing the manifest discovery system in BarModel.js.
How do I access user settings from within my widget?
The BarWidget base class automatically injects a settings object into your QML context. Access it via root.settings (where root is your widget’s ID). This object contains the merged configuration from shell.json and responds to live updates processed by inlineSettingsDelta() in BarModel.js.
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 →