How Plugins Declare Themselves in Omarchy’s Quickshell: The Complete Manifest Guide

Plugins declare themselves by placing a manifest.json file in a plugin directory under $HOME/.config/omarchy/plugins or the bundled firstPartyDir, which shell/services/PluginRegistry.qml validates and registers at startup.

Omarchy’s Quickshell uses a declarative plugin system that loads third-party extensions through structured manifests. When Quickshell initializes, the registry scans designated directories, validates each manifest.json against a strict schema, and registers valid plugins for runtime component resolution. This guide explains exactly how plugins declare themselves according to the omacom/omarchy source code, including the required fields, validation logic, and enablement configuration.

The Plugin Registry Architecture

Quickshell’s plugin discovery begins in shell.qml, which instantiates the PluginRegistry and passes two critical paths: the bundled firstPartyDir containing built-in plugins and the user-wide directory at $HOME/.config/omarchy/plugins. The registry walks each plugin directory, locates the manifest.json file, and validates it before registering the plugin for later lookup.

The registry stores validated manifests in an installedPlugins object and remembers each plugin’s source directory in __sourceDir. When components request a plugin entry point, the entryPointUrl() method (lines 93–107 in shell/services/PluginRegistry.qml) constructs a file URL by combining the source directory with the relative path defined in entryPoints, verifying that the resolved path remains within the plugin folder to prevent directory traversal.

Manifest Schema and Required Fields

Every plugin must provide a manifest.json file conforming to schema version 1. The PluginRegistry.qml validation logic (lines 43–79) enforces strict requirements on field types and values to ensure runtime stability.

Core Manifest Fields

The manifest must include these required fields:

  • schemaVersion – Must be exactly 1.
  • id – Unique identifier string without slashes or .. sequences (validated lines 59–63).
  • name – Human-readable plugin title.
  • version – Semver string indicating release version.
  • kinds – Non-empty array of plugin types (service, panel, bar, menu, etc.).
  • entryPoints – Object mapping each kind to a relative QML file path inside the plugin directory.

Optionally, plugins providing bar widgets may include a barWidget object specifying defaultSection as "left", "center", or "right" to indicate preferred placement.

Validation Logic in PluginRegistry.qml

The registry performs comprehensive validation during the scanning phase:

  1. Schema verification – Checks that the manifest is an object and schemaVersion === 1 (lines 43–52).
  2. Field presence – Ensures all required fields exist and that id contains no leading slashes or parent directory references (lines 59–63).
  3. Kinds validation – Verifies kinds is a non-empty array (lines 64–66).
  4. Entry point safety – Guarantees entryPoints is an object and every path is relative without .. or leading / (lines 68–88).

Defining Entry Points and Source Directories

The entryPoints object maps plugin kinds to QML files relative to the plugin’s root directory. For example, a service plugin points "service" to "Service.qml", while a bar widget maps "bar" to "BarWidget.qml".

When resolving components, entryPointUrl() double-checks that the constructed file URL stays within the plugin’s __sourceDir, preventing escape attempts via path manipulation. This ensures that plugins can only load QML files contained within their own directory tree.

Bar Widget Configuration

Plugins supplying bar widgets may declare default positioning through the optional barWidget object. When present, this configuration specifies the defaultSection where the widget should appear if the user has not overridden placement in shell.json.

{
  "schemaVersion": 1,
  "id": "omarchy.clock",
  "name": "Clock",
  "version": "1.0.0",
  "kinds": ["bar"],
  "entryPoints": {
    "bar": "BarWidget.qml"
  },
  "barWidget": {
    "defaultSection": "center"
  }
}

Enabling Plugins in Configuration

Plugin enablement depends on the isEnabled() method in PluginRegistry.qml (lines 10–38). First-party plugins (located in firstPartyDir) are treated as always enabled unless explicitly disabled. Third-party plugins from $HOME/.config/omarchy/plugins require explicit enablement in shell.json through the plugins array, bar.id, or bar.layout.* entries.

{
  "plugins": [
    { "id": "omarchy.nightlight" },
    { "id": "omarchy.clock" }
  ]
}

When Quickshell reloads configuration, the registry checks these sections to determine which registered plugins should actively load their entry points.

Summary

  • Plugins declare themselves by placing a manifest.json in $HOME/.config/omarchy/plugins or the bundled firstPartyDir.
  • PluginRegistry.qml validates manifests against schema version 1, checking required fields and safe paths at lines 43–79.
  • Entry points must be relative QML files mapped in the entryPoints object; absolute paths and directory traversal are rejected.
  • Optional barWidget configuration specifies default positioning for bar widgets as left, center, or right.
  • First-party plugins enable automatically; third-party plugins require explicit listing in shell.json.

Frequently Asked Questions

Where does Quickshell look for plugin manifests?

Quickshell scans two locations: the bundled firstPartyDir containing Omarchy’s built-in plugins and the user directory at $HOME/.config/omarchy/plugins. The PluginRegistry walks each directory at startup, reading manifest.json files from immediate subdirectories.

What happens if a manifest fails validation?

The registry rejects invalid manifests during the initial scan. Specifically, PluginRegistry.qml (lines 43–79) validates schemaVersion, required field presence, safe id strings, non-empty kinds arrays, and relative entryPoints paths. Failed validations prevent registration, excluding the plugin from the installedPlugins object.

Can entry point paths reference files outside the plugin directory?

No. The entryPointUrl() method (lines 93–107) constructs file URLs by combining the plugin’s __sourceDir with the relative path from entryPoints. It explicitly rejects paths containing .. or leading slashes, ensuring resolved files remain within the plugin’s directory boundary.

How do I disable a first-party plugin?

First-party plugins enable by default, but you can disable them by excluding their id from shell.json configuration sections or adding explicit disable entries where supported. The isEnabled() logic (lines 10–38) treats first-party plugins as enabled unless configuration explicitly overrides this behavior.

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 →