How to Create a Custom Omarchy Shell Plugin Using manifest.json
Omarchy discovers and loads shell extensions by reading a manifest.json file placed in a plugin directory under shell/plugins/, automatically registering the plugin without requiring code-side registration.
Omarchy is an open-source desktop environment that extends its functionality through a dynamic plugin system. By placing a properly structured manifest.json alongside your QML implementation files, you can add custom panels, background services, or widgets to the shell runtime. This guide walks through the exact schema, file placement, and reload mechanisms used by the Omarchy shell loader.
Understanding the manifest.json Schema
The manifest.json file serves as the contract between your plugin and the Omarchy shell runtime (shell.qml). When the shell initializes or reloads, it scans subdirectories under shell/plugins/ for these manifest files and parses them to determine which QML entry points to load.
Required Fields
Every valid manifest must include these top-level keys:
schemaVersion– Currently set to1. This versioning allows the shell loader to handle breaking changes in future Omarchy releases.id– A unique dot-separated identifier, conventionally prefixed withomarchy.(e.g.,omarchy.mycustompanel). This ID must not conflict with existing plugins.name– Human-readable display name shown in plugin listings.version– Semantic version string (e.g.,0.1.0).author– Creator attribution.description– Brief summary of functionality.kinds– An array of strings describing the plugin type(s). Valid values include"service"(background daemon),"panel"(UI bar element), and"widget"(standalone launchable component).entryPoints– An object mapping each kind to its corresponding QML filename. For example,"panel": "Panel.qml"tells the loader to instantiatePanel.qmlwhen registering a panel-type plugin.
Step-by-Step Implementation
Creating a functional plugin requires three components: a directory, a manifest, and a QML entry point. The shell's plugin discovery mechanism handles the rest.
Create the Plugin Directory
Navigate to the appropriate subdirectory under shell/plugins/ based on your plugin type. For a new panel, create a folder under shell/plugins/panels/:
mkdir -p shell/plugins/panels/my-awesome-panel/
Omarchy organizes plugins by category, though the loader will recognize manifests regardless of nested depth. Existing plugins like the Night Light service reside in shell/plugins/services/nightlight/, while the Wi-Fi QR panel lives in shell/plugins/panels/wifiqr/.
Define the manifest.json
Create a manifest.json file in your plugin root with the required schema. The manifest at shell/plugins/services/nightlight/manifest.json demonstrates a service-type plugin, while shell/plugins/panels/wifiqr/manifest.json shows a panel implementation.
Here is a minimal manifest for a custom panel:
{
"schemaVersion": 1,
"id": "omarchy.myawesomepanel",
"name": "My Awesome Panel",
"version": "0.1.0",
"author": "Your Name",
"description": "A custom panel showing whatever you like.",
"kinds": [ "panel" ],
"entryPoints": {
"panel": "Panel.qml"
}
}
The entryPoints object keys must match the values declared in kinds. If you specify multiple kinds, each requires a corresponding entry in entryPoints pointing to its QML implementation file.
Implement the QML Entry Point
Create the QML file referenced in your manifest. This file must exist in the same directory as manifest.json. The entry point acts as the root component that the shell instantiates when loading your plugin.
For a panel plugin, implement a standard QtQuick Item or Rectangle:
import QtQuick 2.15
import QtQuick.Controls 2.15
Item {
width: 200; height: 40
Text {
anchors.centerIn: parent
text: "Hello Omarchy!"
color: "#FFFFFF"
}
}
Save this as Panel.qml alongside your manifest. For service-type plugins, follow the pattern in shell/plugins/services/nightlight/Service.qml, which implements background logic without visual components. Panel plugins should reference the structure in shell/plugins/panels/wifiqr/Panel.qml.
Reload the Shell
Once your files are in place, trigger a plugin reload so the shell runtime recognizes the new manifest. The shell exposes this functionality through the reloadPlugins() function defined in shell/shell.qml (around line 1441).
Execute the reload command from a terminal:
omarchy-reload-shell
Alternatively, any action that triggers shell.reloadPlugins() will refresh the registry. Upon reload, the shell parses your manifest.json, validates the schema, and instantiates the QML components specified in entryPoints.
Key Source Files and Architecture
Understanding the loader implementation helps debug registration issues. The core plugin discovery logic resides in shell/shell.qml, specifically within the loadPluginWidget and reloadPlugins functions. These methods iterate through shell/plugins/, parse JSON manifests, and register valid plugins with the shell's component factory.
Reference implementations provide working templates:
- Service Example:
shell/plugins/services/nightlight/manifest.jsonandService.qmldemonstrate background service architecture. - Panel Example:
shell/plugins/panels/wifiqr/manifest.jsonandPanel.qmlshow UI panel integration. - Loader Logic:
shell/shell.qmlcontains thereloadPlugins()implementation that drives the discovery process.
Summary
- Omarchy uses a declarative
manifest.jsonsystem located inshell/plugins/subdirectories to discover extensions automatically. - The manifest requires
schemaVersion,id,kinds, andentryPointsto map plugin types to their QML implementations. - Valid
kindsinclude"service","panel", and"widget", each requiring a corresponding QML file in theentryPointsobject. - Place QML entry points in the same directory as
manifest.json, matching the filenames specified in the manifest. - Trigger registration by running
omarchy-reload-shell, which invokesreloadPlugins()inshell/shell.qml.
Frequently Asked Questions
What happens if my manifest.json is malformed?
The shell loader in shell/shell.qml validates manifests during the reloadPlugins() scan. If required fields like schemaVersion, id, or entryPoints are missing or invalid, the plugin will be skipped without crashing the shell, though it won't appear in omarchy-list-plugins output. Check the shell logs for JSON parsing errors.
Can a single plugin have multiple kinds?
Yes. Define multiple values in the kinds array (e.g., ["service", "panel"]) and provide corresponding entry points in the entryPoints object. Each kind maps to a separate QML file that the shell instantiates appropriately, allowing hybrid plugins that provide both background logic and UI components.
Where should I place plugin assets like icons?
Store auxiliary files—icons, images, or helper scripts—in your plugin directory alongside manifest.json and your QML files. Reference them using relative paths in your QML code (e.g., "./icon.png"). The shell does not restrict asset placement within the plugin folder, but keeping assets colocated with their manifest ensures portability.
How do I uninstall a custom plugin?
Simply delete the plugin directory from shell/plugins/ and run omarchy-reload-shell. The reloadPlugins() function will remove the plugin from the active registry since the manifest is no longer present on disk. No additional cleanup steps are required in the Omarchy configuration.
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 →