# How the Quickshell Plugin System Works in Omarchy: A Deep Dive into Manifest-Based Extension Architecture

> Discover Quickshell's manifest-driven plugin system in Omarchy. Learn how extensions declare capabilities and integrate seamlessly into the shell via widgets, panels, and more.

- Repository: [Omacom/omarchy](https://github.com/omacom/omarchy)
- Tags: deep-dive
- Published: 2026-09-02

---

**Quickshell uses a manifest-driven plugin system where extensions declare their capabilities in a [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) file, are discovered by a `PluginRegistry` service, and integrate into the shell through declared entry points for bar widgets, panels, overlays, menus, and services.**

The Quickshell plugin architecture powers Omarchy Quattro's extensibility, enabling both first-party bundled extensions and third-party user plugins to safely extend the desktop environment. The system centers on the **PluginRegistry** service in `shell/services/PluginRegistry.qml`, which orchestrates discovery, validation, and lifecycle management while enforcing security boundaries that prevent plugins from escaping their sandbox.

## Plugin Discovery and Directory Scanning

The shell initializes plugin discovery during startup by instantiating `PluginRegistry` in `shell/shell.qml`:

```qml
property PluginRegistry pluginRegistry: PluginRegistry { }

```

When `PluginRegistry.rescan()` is invoked, it executes a Bash script that traverses two distinct directories:

- **First-party plugins**: `shell/plugins/` (bundled with the Omarchy repository)
- **User plugins**: `~/.config/omarchy/plugins/`

The scanning script emits structured blocks for each discovered [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json), using a delimiter format of `===<kind>::<dir>=== … === EOM ===`. From `PluginRegistry.qml` lines 62-71:

```qml
function rescan() {
    // ...
    var script = ""
        + "emit_manifest() { ... }"
        + "scan_firstparty() { ... }"
        + "scan_thirdparty() { ... }"
        + "scan_firstparty \"$0\"; "
        + "scan_thirdparty \"$1\""
    scanProcess.command = ["bash", "-c", script,
                         registry.firstPartyDir, registry.pluginsDir]
    scanProcess.running = true
}

```

The `parseScanOutput()` method (lines 52-115) processes this output, JSON-parses each manifest, enriches it with `__sourceDir` and `__isFirstParty` metadata, and passes it to `validateManifest()`. Invalid manifests are rejected with warnings, and valid discoveries populate the internal `installedPlugins` map.

## Manifest Structure and Validation

Every Quickshell plugin must provide a [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) conforming to schema version 1. The `validateManifest()` function (lines 43-89) enforces strict validation rules:

| Validation Check | Purpose |
|-----------------|---------|
| `schemaVersion === 1` | Ensures manifest compatibility with current system |
| Required fields (`id`, `name`, `version`, `kinds`, `entryPoints`) | Guarantees minimal operational metadata |
| `id` as simple, non-path string | Prevents filesystem traversal and namespace collisions |
| `kinds` as non-empty array | Determines which shell subsystems will load the plugin |
| `entryPoints` as plain object with **relative** paths via `isSafeEntryPoint` | Blocks directory escape attempts |
| `barWidget.defaultSection` ∈ {`left`, `center`, `right`} (optional) | Controls default bar widget placement |

Failed validation prints a warning and excludes the plugin from the registry.

### Declaring Plugin Kinds and Entry Points

The `kinds` array declares which integration points a plugin provides. Valid kinds include:

- `bar` — standalone bar implementations
- `bar-widget` — components that embed in the main bar
- `service` — shared singleton services
- `panel` — toggleable side panels
- `overlay` — fullscreen or semi-transparent overlays
- `menu` — popup menu interfaces

Each kind specified in `kinds` must have a corresponding entry in `entryPoints` mapping to a relative QML file path.

## Enabling and Disabling Plugins

Plugin activation state is managed through `PluginRegistry.setEnabled(id, value, placement?)` (lines 49-42). The system distinguishes between discovery and activation:

- **First-party plugins**: Enabled by default in [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json)
- **Third-party plugins**: Must be explicitly added to `plugins[]` or removed from `disabledPlugins[]`

The IPC handler in `shell.qml` lines 11-19 exposes this to external tools:

```qml
function enablePlugin(id: string, placementJson: string): string {
    try {
        var placement = JSON.parse(placementJson || "{}")
        if (shell.pluginRegistry.setEnabled(id, true, placement)) return "ok"
        return shell.pluginRegistry.lastEnableError || "unknown"
    } catch (e) {
        return "invalid placement: " + e
    }
}

```

## Bar Widget Integration Architecture

Bar widgets receive specialized handling through the **BarWidgetRegistry** (`shell/services/BarWidgetRegistry.qml`). When a `bar-widget` kind plugin is enabled:

1. `syncPluginWidgets()` (lines 69-179) instantiates a `Component` from `entryPoints.barWidget`
2. The component registers via `barWidgetRegistry.register(id, component, meta)`
3. The bar UI loads the widget through its `sourceUrl` and positions it according to `defaultBarWidgetSection()` or explicit placement

The convenience method `putBarWidget()` (lines 23-31) adds widgets idempotently—only if not already present on the bar:

```bash

# CLI equivalent: Add clock widget to right section

omarchy plugin putBarWidget example.clock '{"section":"right"}'

```

Repositioning uses `moveBarWidget()` with `before` or `section` placement directives:

```bash

# Move clock before workspaces widget

omarchy plugin moveBarWidget example.clock '{"before":"omarchy.workspaces"}'

```

## Services, Panels, Overlays, and Menus

Beyond bar widgets, the same manifest system supports additional integration patterns:

- **Services**: Singletons loaded once and shared across plugins via `ensureService()` / `serviceFor()` (around line 84)
- **Panels/Overlays/Menus**: On-demand instantiation through the **Instantiator** in `shell.qml` (lines 81-154)

Entry point resolution uses `entryPointUrl(manifest, kind)` (lines 93-108) to construct sandboxed URLs. Each loaded component receives injected context properties:

```qml
property string omarchyPath
property var shell
property var manifest
property var barWidgetRegistry
property var pluginRegistry
property var service

```

## Hot-Reload and Runtime Lifecycle

The Quickshell plugin system supports development workflows through Inotify-based hot-reload:

1. File system watcher monitors `~/.config/omarchy/plugins/`
2. Changes trigger `localPluginChanged()` → `localPluginReloadTimer` (debounced)
3. `reloadPlugins()` unloads active panels, services, and widgets
4. Full rescan reconstitutes the plugin set with updated code

This cycle preserves shell stability while allowing rapid iteration on third-party plugins.

## Public IPC API for External Tools

The shell exposes a minimal RPC surface via `IpcHandler` with target `"shell"` in `shell.qml`. These methods enable CLI and UI integration:

| Method | Function |
|--------|----------|
| `listPlugins()` | JSON array of all plugins with status, kinds, and metadata |
| `setPluginEnabled(id, enabled)` | Direct toggle of plugin activation |
| `enablePlugin(id, placementJson)` | Enable with optional positioning |
| `putBarWidget(id, placementJson)` | Idempotent widget placement |
| `moveBarWidget(id, placementJson)` | Reorder existing widget |
| `summon(id, payloadJson)` | Open panel/overlay/menu or activate bar widget |
| `hide(id)` | Close panel or hide bar widget |
| `toggle(id, payloadJson)` | Convenience open/close switch |

CLI usage examples demonstrate typical interactions:

```bash

# Enable a third-party panel

omarchy plugin enable example.weather '{}'

# Summon network panel with custom payload

omarchy plugin summon omarchy.network '{"action":"show"}'

# List all discovered plugins

omarchy plugin list

```

## Key Source Files and Responsibilities

| File | Role |
|------|------|
| `shell/services/PluginRegistry.qml` | Core registry: scanning, validation, enable/disable, entry-point resolution |
| `shell/shell.qml` | Host orchestration: registry instantiation, file watching, IPC wiring, service lifecycle |
| `shell/services/BarWidgetRegistry.qml` | Bar widget component storage and metadata management |
| `shell/services/AppLibrary.qml` | Application launching API for plugin consumption |
| `Bar.qml` | UI container consuming `BarWidgetRegistry` entries (referenced from shell.qml) |

## Summary

- **Quickshell plugins** declare capabilities through a validated [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) with schema version 1, supported kinds, and sandboxed entry points.
- **PluginRegistry** in `shell/services/PluginRegistry.qml` handles discovery via Bash directory scanning, manifest validation, and enablement state management.
- **Security boundaries** enforce relative entry point paths and simple plugin IDs to prevent filesystem escapes and namespace collisions.
- **Bar widgets** integrate through `BarWidgetRegistry` with placement controls for left/center/right sections and dynamic repositioning.
- **Hot-reload** via Inotify enables rapid plugin development without shell restart.
- **IPC methods** exposed through `shell.qml` provide CLI-accessible control over the full plugin lifecycle.

## Frequently Asked Questions

### How does Quickshell discover third-party plugins?

Quickshell scans `~/.config/omarchy/plugins/` for [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) files during `PluginRegistry.rescan()`. The scan executes a Bash script that emits structured output for each discovery, which `parseScanOutput()` processes into validated plugin entries.

### What prevents malicious plugins from accessing files outside their directory?

The `validateManifest()` function enforces `isSafeEntryPoint` checks that reject absolute paths or directory traversal sequences in `entryPoints` values. All entry points must be relative paths within the plugin's source directory, ensuring sandbox containment.

### Can plugins be enabled without editing configuration files?

Yes. The `enablePlugin(id, placementJson)` IPC method and corresponding `omarchy plugin enable` CLI command programmatically activate plugins. First-party plugins start enabled by default; third-party plugins require explicit enablement through this API or manual [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json) editing.

### How do I develop a plugin with live reload?

Place your plugin in `~/.config/omarchy/plugins/` during development. The shell's Inotify watcher detects changes, debounces through `localPluginReloadTimer`, and executes `reloadPlugins()` to unload and re-scan your code without restarting Quickshell.