Understanding the Quickshell Plugin System with Manifests and Kinds
Omarchy's Quickshell UI uses a manifest-driven plugin architecture where JSON descriptors define plugin capabilities, entry points, and kinds, validated by the PluginRegistry service before exposure to the shell.
Understanding the Quickshell plugin system with manifests and kinds is essential for extending Omarchy's desktop environment safely. The architecture separates first-party bundled extensions from user-installed third-party plugins through a rigorous validation pipeline implemented in shell/services/PluginRegistry.qml. Every plugin must declare its kinds—functional classifications that determine how the shell loads and renders the component.
How the PluginRegistry Scans and Validates Manifests
The PluginRegistry service operates as the central authority for plugin discovery and validation. It scans two distinct locations during initialization:
- First-party plugins – Bundled with Omarchy and located in the
firstPartyDirdirectory - Third-party plugins – User-installed extensions under
~/.config/omarchy/plugins
During a rescan operation, the registry launches a Bash script that emits each plugin's manifest.json as a discrete block of text. The parseScanOutput method (lines 76‑124 in shell/services/PluginRegistry.qml) receives this output, parses the JSON, and validates the structure.
Required Manifest Fields
Validation guarantees that every manifest contains these required keys:
| Field | Description | Constraints |
|---|---|---|
id |
Unique plugin identifier | Must not contain / or .. characters |
name |
Human-readable title | Displayed in UI selectors |
version |
Semantic version string | Parsed for compatibility checks |
kinds |
Functional classification array | Determines valid entry points |
entryPoints |
Map of kind → relative file path | Path must stay within plugin directory |
Optional Configuration Sections
Beyond required fields, manifests may include:
barWidget– SpecifiesdefaultSectionas"left","center", or"right"for automatic placementomarchymetadata – Declares host capabilities such as"authentication"for privileged operations
Internal Metadata Stamping
After validation, the registry modifies the manifest map with two internal flags before adding it to installedPlugins:
__isFirstParty– Boolean indicating bundled status__hostCapabilities– Inherited capabilities when third-party plugins clone first-party functionality
Understanding Plugin Kinds and Entry Points
The kinds array classifies plugins by their functional role in the shell. This classification determines valid entry points and UI placement options.
Supported Plugin Kinds
| Kind | Purpose | Typical Entry Point Key |
|---|---|---|
bar |
Full bar configuration replacing the entire bar | bar |
bar-widget |
Widget living within the bar layout | barWidget |
panel |
Full-screen or tiled panel interface | panel |
service |
Background daemon or persistent service | service |
menu |
Top-level menu (often paired with bar-widgets) | menu |
idle |
Idle-time task or screensaver component | idle |
Entry Point Resolution
The entryPointUrl(manifest, kind) function (lines 118‑132) constructs safe file:// URLs by resolving the relative path from entryPoints against the plugin's source directory. The method isSafeEntryPoint (lines 36‑40) rejects absolute paths, parent directory references (..), and empty strings before resolution.
Core Plugin Lifecycle Workflow
The Quickshell plugin system follows a five-stage pipeline:
- Rescan –
PluginRegistry.rescan()builds the discovery Bash script, executes it, and captures stdout containing manifest blocks - Parse –
parseScanOutputsplits the text into individual manifests, validates JSON structure, and populates theinstalledPluginsmap - Entry-point resolution –
entryPointUrl()constructs validated file URLs ensuring paths remain within the plugin sandbox - Enable/disable –
setEnabled(id, value, placement)(lines 74‑86) updatesshell.json, manages theplugins[]anddisabledPlugins[]arrays, and handles bar widget cloning logic viamoveBarEntryandbarTargetfunctions - Shell consumption – The central
shell.qmlmodule queries the registry throughbarManifestFor(id)(lines 194‑197) andisBarOptionManifest(manifest)(lines 199‑205) to load appropriate QML components
Security and Sandbox Guarantees
Omarchy implements multiple safety layers to prevent plugin escalation:
- Path safety –
isSafeEntryPointvalidates that resolved paths remain within the plugin's source directory (lines 124‑131) - Namespace protection – Third-party plugins cannot use IDs starting with
omarchy.; the parser warns and discards such manifests (lines 30‑35) - URL validation –
entryPointUrlensures returned URLs always begin with the plugin's root directory, returning empty strings for unsafe resolutions
These checks prevent malicious plugins from escaping their sandbox or hijacking built-in components through path traversal attacks.
Practical Implementation Examples
Creating a Minimal Bar Widget Manifest
Place this manifest.json under ~/.config/omarchy/plugins/my.clock/manifest.json:
{
"id": "my.clock",
"name": "Clock Widget",
"version": "1.0.0",
"kinds": ["bar-widget"],
"entryPoints": {
"bar-widget": "Widget.qml"
},
"barWidget": {
"defaultSection": "right"
}
}
The registry exposes the widget at file:///home/USER/.config/omarchy/plugins/my.clock/Widget.qml after the next rescan.
Enabling Plugins Programmatically
From within QML, enable a plugin and define its bar placement:
Component.onCompleted: {
// Place widget before the system tray in the right section
var placement = { before: "omarchy.tray", section: "right" };
var success = shell.pluginRegistry.setEnabled("my.clock", true, placement);
if (!success) console.error("Failed to enable my.clock");
}
Under the hood, setEnabled updates config.bar.layout.right and manages the active plugin registry.
Resolving Entry Point URLs
Access plugin entry points safely through the registry API:
var manifest = shell.pluginRegistry.installedPlugins["my.panel"];
var url = shell.pluginRegistry.entryPointUrl(manifest, "panel");
console.log("Panel source URL:", url);
This guarantees the URL points inside the plugin directory or returns an empty string if validation fails.
Summary
- The PluginRegistry in
shell/services/PluginRegistry.qmlvalidates all manifests before exposing them to the shell - Every plugin requires
id,name,version,kinds, andentryPointsfields inmanifest.json - Kinds determine functional roles:
bar,bar-widget,panel,service,menu, andidle - Entry points are resolved through
entryPointUrl()with path traversal protection viaisSafeEntryPoint() - Third-party plugins live in
~/.config/omarchy/pluginsand cannot use theomarchy.ID prefix - The registry stamps manifests with
__isFirstPartyand__hostCapabilitiesfor internal tracking
Frequently Asked Questions
What fields are required in a Quickshell manifest.json?
Every manifest must include id (unique identifier without / or ..), name (display title), version (semantic version), kinds (array of functional types), and entryPoints (map of kind to relative file paths). Optional fields include barWidget for layout hints and omarchy for host capability declarations.
How does Omarchy prevent malicious plugins from accessing system files?
The system employs isSafeEntryPoint (lines 36‑40) to reject absolute paths and parent directory references before resolution. The entryPointUrl function further validates that resolved absolute paths start with the plugin's source directory (lines 124‑131). Third-party plugins cannot impersonate system plugins by using IDs starting with omarchy..
Can third-party plugins override built-in Omarchy components?
No. The namespace protection in parseScanOutput (lines 30‑35) explicitly warns and discards any third-party manifest with an ID prefix of omarchy.. First-party plugins bundled in the firstPartyDir receive the __isFirstParty flag, ensuring privileged components remain distinct from user-installed extensions.
What is the difference between bar and bar-widget kinds?
The bar kind represents a complete bar configuration that replaces the entire shell bar, using the bar entry point. The bar-widget kind represents individual widgets that live within the bar layout, using the barWidget entry point and supporting defaultSection placement in left, center, or right zones.
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 →