# How Quickshell Plugins Are Declared and Managed in Omarchy

> Discover how Quickshell plugins are declared and managed in Omarchy using a manifest-driven registry. Learn about manifest.json and PluginRegistry for seamless plugin integration.

- Repository: [37signals/omarchy](https://github.com/basecamp/omarchy)
- Tags: internals
- Published: 2026-08-26

---

**Quickshell plugins in the Omarchy desktop environment use a manifest-driven registry system where each plugin directory contains a [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) file declaring its identity, supported kinds, and entry points, all managed at runtime by the `PluginRegistry` component in `shell/services/PluginRegistry.qml`.**

The Quickshell plugin architecture in basecamp/omarchy centers on a declarative manifest system that enables dynamic discovery, validation, and lifecycle management of both first-party bundled extensions and third-party user plugins. At startup, the shell instantiates a centralized `PluginRegistry` that scans designated directories, validates manifest schemas, and exposes helper methods for enabling, disabling, and positioning plugins within the desktop shell.

## Plugin Manifest Structure

Every Quickshell plugin is defined by a **[`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json)** file located in the plugin's root directory. This manifest declares the plugin's unique identifier, version, supported "kinds" (such as `service`, `bar-widget`, or `panel`), and entry-point file paths.

A minimal panel plugin manifest looks like this:

```json
{
  "id": "example.panel",
  "name": "Example Panel",
  "version": "1.0.0",
  "schemaVersion": 1,
  "kinds": ["panel"],
  "entryPoints": {
    "service": "Service.qml"
  }
}

```

The **`kinds`** array determines what types of shell components the plugin provides, while **`entryPoints`** maps each kind to its corresponding QML implementation file. For bar widgets, you may optionally include a **`barWidget.defaultSection`** field (`left`, `center`, or `right`) to specify initial placement.

## Plugin Discovery and Registry Initialization

When the shell initializes in `shell.qml`, it creates an instance of `PluginRegistry` and wires it to the shell configuration:

```qml
property PluginRegistry pluginRegistry: PluginRegistry { }
...
pluginRegistry.firstPartyDir = shell.firstPartyPluginsDir
pluginRegistry.shellConfigProvider = function() { return shell.shellConfig }
pluginRegistry.shellConfigMutator = function(mutate) { shell.mutateShellConfig(mutate) }
pluginRegistry.rescan()

```

The **`rescan()`** method triggers a scan of two distinct locations:

- **First-party plugins** bundled with Omarchy (located under `shell/plugins/…`)
- **Third-party plugins** installed by the user in `~/.config/omarchy/plugins`

Scanning is performed by a Bash script that emits raw manifest JSON surrounded by markers. The `PluginRegistry.parseScanOutput` function processes this output, while **`validateManifest()`** enforces schema compliance and security constraints before storing valid plugins in the `installedPlugins` map.

## Plugin Lifecycle Management

### Enabling and Disabling Plugins

Plugin state is persisted in [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json). The registry provides **`isEnabled(id)`**, **`setEnabled(id, value, placement)`**, and related helpers to manipulate this configuration. When you enable a plugin, `setEnabled` invokes the wired `shellConfigMutator` to add entries to the `plugins[]` array or update bar configurations.

To enable a plugin programmatically:

```qml
if (shell.pluginRegistry) {
  var ok = shell.pluginRegistry.setEnabled("example.panel", true)
  console.log(ok ? "Enabled" : "Failed")
}

```

Disabling follows the same pattern:

```qml
shell.pluginRegistry.setEnabled("example.panel", false)

```

### Bar Widget Placement

For plugins of kind `bar-widget`, the registry provides **`putBarWidget(id, placement)`** and **`moveBarEntry`** to manage layout positioning. The manifest may specify a **`barWidget.defaultSection`** field, but you can override this when enabling the widget.

A bar widget manifest includes placement metadata:

```json
{
  "id": "example.widget",
  "name": "Example Widget",
  "version": "1.0.0",
  "schemaVersion": 1,
  "kinds": ["bar-widget"],
  "entryPoints": {
    "barWidget": "BarWidget.qml"
  },
  "barWidget": {
    "defaultSection": "right"
  }
}

```

To place the widget after a specific element:

```qml
var placement = { after: "omarchy.tray" }
shell.pluginRegistry.putBarWidget("example.widget", placement)

```

## Security and Validation

Before a plugin enters the `installedPlugins` map, **`validateManifest()`** performs several security checks:

- Verifies the schema version matches the expected format
- Ensures required fields (id, name, version, kinds) are present
- Confirms entry-point paths are safe and remain within the plugin's source directory via **`entryPointUrl(manifest, kind)`**
- Blocks plugin IDs that conflict with the reserved `omarchy.*` namespace

The **`entryPointUrl`** function builds file URLs only after confirming the resolved path does not escape the plugin directory, preventing directory traversal attacks.

## Configuration Integration

The registry uses dependency injection for configuration access via **`shellConfigProvider`** and **`shellConfigMutator`**. These callbacks allow `PluginRegistry` to read and write the canonical [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) without performing direct file I/O, ensuring consistency with the shell's state management.

All plugin operations increment **`registryRevision`** to trigger UI refreshes, while signals such as **`pluginsChanged`**, **`pluginLoadFailed`**, and **`localPluginChanged`** notify other components of state changes.

## Summary

- Quickshell plugins require a **[`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json)** declaring the plugin ID, version, kinds, and entry points.
- The **`PluginRegistry`** in `shell/services/PluginRegistry.qml` manages discovery, validation, and lifecycle across first-party and third-party plugin directories.
- **`rescan()`** executes a Bash script to discover manifests, while **`validateManifest()`** enforces security constraints and schema compliance.
- Enabling and disabling plugins modifies **[`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json)** through the **`setEnabled`** helper and its wired mutator functions.
- Bar widgets support positional placement via **`putBarWidget`** and **`barTarget`**, with default sections defined in the manifest.
- The registry prevents unsafe path resolution and reserves the `omarchy.*` namespace for built-in components.

## Frequently Asked Questions

### How do I install a third-party Quickshell plugin in Omarchy?

Place the plugin directory containing a valid [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) into `~/.config/omarchy/plugins/`. Run `rescan()` or restart the shell to discover the new plugin. The `PluginRegistry` will automatically validate the manifest and add it to the `installedPlugins` map, making it available for enabling via the shell configuration.

### What fields are required in a Quickshell plugin manifest?

A valid manifest must include **`id`**, **`name`**, **`version`**, **`schemaVersion`**, and **`kinds`**. The **`entryPoints`** object must map each declared kind to a relative file path. For `bar-widget` kinds, you may optionally include **`barWidget.defaultSection`** to specify initial placement (`left`, `center`, or `right`).

### How does the PluginRegistry prevent malicious plugins from accessing files outside their directory?

The registry validates entry-point paths through **`entryPointUrl(manifest, kind)`**, which resolves the absolute path and confirms it remains within the plugin's source directory. This prevents directory traversal attacks. Additionally, **`validateManifest()`** blocks plugin IDs using the reserved `omarchy.*` namespace and checks that all file references are safe before allowing the plugin to load.

### Can I programmatically move a bar widget after it has been enabled?

Yes. Use **`shell.pluginRegistry.moveBarEntry(id, placement)`** or **`putBarWidget(id, placement)`** to reposition widgets within the bar layout. The placement object accepts properties like `after` or `before` to specify relative positioning, and the registry updates both the UI state and the underlying [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) configuration automatically.