# Omarchy Quickshell Plugin System: Architecture, Discovery, and Runtime Loading

> Discover the Omarchy Quickshell plugin system. Learn how it loads plugins at runtime using JSON manifests for widgets, panels, and services in a reactive QML shell.

- Repository: [Omacom/omarchy](https://github.com/omacom/omarchy)
- Tags: architecture
- Published: 2026-08-30

---

**The Omarchy Quickshell plugin system loads first-party and third-party plugins at runtime via `PluginRegistry.qml`, using JSON manifests to register bar widgets, panels, and services into a reactive QML shell environment.**

The Omarchy Quickshell plugin system provides a declarative extension mechanism for the `omacom/omarchy` desktop shell, allowing developers to inject custom UI components and background services without modifying core source code. Built on Qt's QML framework, the system differentiates between bundled first-party plugins located in `shell/plugins/` and user-installed third-party plugins stored in `~/.config/omarchy/plugins`. Each plugin exposes its capabilities through a validated [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) file that declares entry points, versioning, and widget metadata.

## Plugin Manifest Structure and Validation

Every Omarchy Quickshell plugin requires a **manifest.json** (or `*.manifest.json`) that describes its runtime contract. The manifest must declare a unique `id` (e.g., `omarchy.clock`), a human-readable `name`, a semantic `version`, and a `kinds` array that categorizes the plugin's functionality (e.g., `["bar-widget"]`, `["service"]`, `["panel"]`).

The `entryPoints` field maps each kind to a relative path pointing to the implementing QML component. For bar widgets, an optional `barWidget` object specifies default placement metadata such as the `defaultSection` (`left`, `center`, or `right`).

Validation occurs in `shell/services/PluginRegistry.qml`, which enforces that manifests are plain objects with required fields and that all `entryPoints` are safe relative paths containing no `..` sequences or leading slashes. Upon successful validation, the registry injects internal stamps `__sourceDir` and `__isFirstParty` to distinguish built-in components from user installations.

## Core Registry Components

The plugin system relies on three primary QML singletons that manage lifecycle and UI integration:

- **`PluginRegistry`** (`shell/services/PluginRegistry.qml`): The central authority that scans plugin directories, maintains the `installedPlugins` model, and exposes imperative APIs including `setEnabled(id, value, placement?)`, `moveBarWidget`, and `putBarWidget`. It handles URL resolution for entry points and manages the `disabledPlugins` list for first-party components.

- **`BarWidgetRegistry`** (`shell/services/BarWidgetRegistry.qml`): A specialized registry that maintains the catalogue of available bar widgets. Plugins register their UI components via `register(id, component, metadata)`, storing them in the `widgets` property for instantiation by the bar layout engine.

- **`ShellRoot`** (`shell/shell.qml`): The bootstrap component that instantiates both registries, wires them to configuration providers (`shellConfigProvider`, `shellConfigMutator`), and triggers the initial plugin scan at application startup.

## Plugin Discovery and Directory Scanning

Plugin discovery follows a strict two-phase scanning process controlled by `PluginRegistry`:

1. **Directory Preparation**: `PluginRegistry.ensureUserDir()` creates `~/.config/omarchy/plugins` if absent, ensuring a valid target for third-party installations.

2. **Rescan Execution**: `PluginRegistry.rescan()` spawns a Bash script that walks the first-party directory (`shell/plugins`) and the third-party directory (`~/.config/omarchy/plugins`) one level deep. The script emits delimited output streams formatted as `===firstparty::/path/to/plugin=== … JSON … === EOM ===`.

`PluginRegistry.parseScanOutput()` parses this stream, validates each manifest, and merges results into the `installedPlugins` property. The registry also initializes `localPluginWatcher`, which uses `inotifywait` to monitor the user plugin directory for additions, removals, or modifications, triggering automatic rescans when filesystem changes occur.

## Enabling and Disabling Plugins

The canonical API for toggling plugin state is `PluginRegistry.setEnabled(id, value, placement?)`, which mutates the runtime configuration based on plugin kind:

- **Bar Widgets** (`kinds` contains `"bar-widget"`): Insert or remove entries from `shellConfig.bar.layout` within specific sections (left, center, right). The optional `placement` parameter accepts `before`, `after`, `section`, or `index` to control positioning relative to existing widgets.

- **Bar Containers** (`kinds` contains `"bar"`): Toggle the `shellConfig.bar.id` property to switch between different bar implementations.

- **Services, Panels, and Overlays**: Add or remove plugin IDs from the top-level `plugins[]` array in the configuration object.

For first-party plugins, disabling adds the ID to `disabledPlugins[]` rather than removing the entry entirely, preserving the ability to re-enable bundled components without filesystem lookup.

## Bar Widget Registration and Placement

When a bar-widget plugin loads, its QML component executes registration against the widget catalogue:

```qml
BarWidgetRegistry.register("omarchy.clock", ClockComponent, { defaultSection: "center" })

```

This call, typically found in the plugin's `Panel.qml` or `Service.qml`, stores the component reference and metadata in `BarWidgetRegistry.widgets`. The bar UI iterates over `BarWidgetRegistry.availableIds()` to instantiate visible widgets according to the current layout configuration.

Developers can programmatically reposition widgets using `moveBarWidget`:

```qml
registry.moveBarWidget("omarchy.clock", {
    before: "omarchy.audio",
    section: "right"
})

```

## Configuration and Runtime Updates

User configuration persists in `~/.config/omarchy/shell.json`, a JSON file that the `ShellRoot` monitors for changes. A minimal configuration enabling a third-party plugin appears as:

```json
{
  "version": 1,
  "plugins": [{ "id": "my.custom.plugin" }]
}

```

For precise bar widget placement, the configuration supports relational positioning:

```json
{
  "version": 1,
  "bar": {
    "layout": {
      "right": [{ "id": "my.custom.widget", "before": "omarchy.tray" }]
    }
  }
}

```

When [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json) changes, `ShellRoot` increments `registryRevision` and emits `pluginsChanged()`, causing the registries to re-evaluate enabled states and the UI to refresh reactive bindings.

## Summary

- **Manifest-driven architecture**: Plugins declare capabilities via [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) with validated `entryPoints` and `kinds` arrays.
- **Dual source support**: The system loads first-party plugins from `shell/plugins/` and third-party plugins from `~/.config/omarchy/plugins`.
- **Centralized registries**: `PluginRegistry.qml` handles discovery and lifecycle, while `BarWidgetRegistry.qml` manages UI component catalogues.
- **Dynamic enabling**: The `setEnabled()` API supports granular control over bar widgets, services, and panels with optional placement metadata.
- **Reactive configuration**: Changes to `~/.config/omarchy/shell.json` trigger immediate UI updates via filesystem monitoring and QML property binding.

## Frequently Asked Questions

### How does Omarchy Quickshell validate plugin manifests?

`PluginRegistry.qml` validates that each [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) is a plain object containing required fields (`id`, `name`, `version`, `kinds`, `entryPoints`) and ensures all entry point paths are relative without directory traversal sequences (`..` or leading `/`). Invalid manifests are rejected during the scan phase.

### Can I install third-party plugins without modifying the core repository?

Yes. Place any third-party plugin directory containing a valid [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) into `~/.config/omarchy/plugins`. The `PluginRegistry` automatically detects these via directory scanning and `inotifywait` monitoring, making them available for enabling through the configuration API or [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json) edits.

### What happens when I disable a first-party plugin versus a third-party plugin?

Disabling a first-party plugin adds its ID to the `disabledPlugins[]` array while retaining the manifest in memory, allowing immediate re-enabling without rescanning. Disabling a third-party plugin removes its entry from the active configuration entirely, requiring a subsequent rescan if the plugin files remain in the user directory.

### How do I programmatically reposition a bar widget in the Omarchy Quickshell?

Use `PluginRegistry.moveBarWidget(id, placement)` where the placement object specifies `section` (left, center, right) and positional hints such as `before` or `after` referencing other widget IDs. This mutates the `shellConfig.bar.layout` and triggers immediate UI recomposition.