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 firstPartyDir directory
  • 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 – Specifies defaultSection as "left", "center", or "right" for automatic placement
  • omarchy metadata – 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:

  1. Rescan – PluginRegistry.rescan() builds the discovery Bash script, executes it, and captures stdout containing manifest blocks
  2. Parse – parseScanOutput splits the text into individual manifests, validates JSON structure, and populates the installedPlugins map
  3. Entry-point resolution – entryPointUrl() constructs validated file URLs ensuring paths remain within the plugin sandbox
  4. Enable/disable – setEnabled(id, value, placement) (lines 74‑86) updates shell.json, manages the plugins[] and disabledPlugins[] arrays, and handles bar widget cloning logic via moveBarEntry and barTarget functions
  5. Shell consumption – The central shell.qml module queries the registry through barManifestFor(id) (lines 194‑197) and isBarOptionManifest(manifest) (lines 199‑205) to load appropriate QML components

Security and Sandbox Guarantees

Omarchy implements multiple safety layers to prevent plugin escalation:

  • Path safety – isSafeEntryPoint validates 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 – entryPointUrl ensures 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.qml validates all manifests before exposing them to the shell
  • Every plugin requires id, name, version, kinds, and entryPoints fields in manifest.json
  • Kinds determine functional roles: bar, bar-widget, panel, service, menu, and idle
  • Entry points are resolved through entryPointUrl() with path traversal protection via isSafeEntryPoint()
  • Third-party plugins live in ~/.config/omarchy/plugins and cannot use the omarchy. ID prefix
  • The registry stamps manifests with __isFirstParty and __hostCapabilities for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →