# How Plugins Work in the Omarchy Quickshell Desktop: Architecture and Implementation

> Explore how Omarchy Quickshell desktop plugins work. Learn about its file-based system, JSON manifests, registry components, and configuration for seamless extension management.

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

---

**TLDR:** Omarchy uses a file-based plugin system where JSON manifests define entry points, `PluginRegistry.qml` discovers and validates extensions, and `BarWidgetRegistry.qml` manages bar widgets, with activation state controlled through [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) configuration.

The Omarchy desktop environment extends Quickshell with a modular architecture that separates core functionality from community extensions. Understanding how plugins work in the Omarchy Quickshell desktop reveals a security-first design that validates all third-party code before execution while supporting dynamic configuration changes. The system relies on two primary registries and a strict manifest schema to balance flexibility with sandboxed safety.

## Plugin Discovery and Manifest Validation

When the desktop initializes, `shell/services/PluginRegistry.qml` instantiates and scans two distinct directories for available extensions. The registry distinguishes between **first-party plugins** bundled in the repository via `firstPartyDir` and **user-installed plugins** located at `~/.config/omarchy/plugins` via `pluginsDir`.

### Directory Structure and Plugin Types

Each plugin directory contains subfolders with a mandatory [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) file conforming to schema version 1. According to the basecamp/omarchy source code, the registry processes all discovered manifests through the `validateManifest()` function located at lines 43-90 in `PluginRegistry.qml`. This validation ensures required fields including `id`, `name`, `version`, `kinds`, and `entryPoints` are present and correctly formatted.

### Security Validation and Path Sanitization

The validation logic enforces several safety constraints. The `id` must be a safe string containing no slashes or `..` sequences, while `kinds` must be a non-empty array specifying the plugin type such as `"service"`, `"panel"`, or `"bar"`. The `isSafeEntryPoint()` helper at lines 36-40 verifies that all entry-point paths remain relative and stay within the plugin's source directory, preventing directory traversal attacks. Valid manifests are stored in the `installedPlugins` property as a keyed object.

## Resolving Entry Points and Security Boundaries

Plugins expose functionality through **entry points** defined in their manifests, such as `"service"` mapping to `Service.qml`. The `entryPointUrl(manifest, kind)` function at lines 93-108 constructs a `file://` URL for the requested component. This method performs a defense-in-depth check to ensure the resolved path does not escape the plugin's sandbox directory before returning the URL for dynamic loading.

## Enabling and Disabling Plugins via Configuration

A plugin becomes active only when referenced in the user's [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) configuration file. The `isEnabled(id)` method at lines 110-138 implements the activation logic with kind-specific rules.

### Bar Plugin Activation

For bar plugins, the `config.bar.id` value must match the plugin's identifier exactly. Other plugin kinds activate when appearing in `bar.layout.*` for bar widgets or the `plugins[]` array for panels, overlays, and services.

### First-Party Plugin Defaults

First-party plugins receive special treatment: they are implicitly enabled unless explicitly listed in the `disabledPlugins[]` array, as implemented at lines 135-137. The `findEntryLocation()` function traverses the configuration to locate plugin references, enabling the registry to emit `pluginsChanged` signals when the active set modifications occur.

## Bar Widget Registration Architecture

Bar widgets represent a specialized plugin category that renders on the top bar. These components register through `shell/services/BarWidgetRegistry.qml`, which maintains a `widgets` map storing each widget's component and metadata. Plugins invoke the `register(id, component, metadata)` function at lines 16-25 to announce their widgets:

```qml
// Example: Registering a Clock bar widget
import QtQuick 2.15
import Quickshell 1.0

Component {
    id: clockWidget
    
    // Widget implementation...
    
    Component.onCompleted: {
        BarWidgetRegistry.register("omarchy.clock", clockWidget, {
            defaultSection: "right",
            label: "Clock"
        })
    }
}

```

The registry tracks a `revision` number to notify the bar of additions or removals, enabling reactive UI updates without full restarts.

## Runtime Component Loading and Wiring

When instantiating a plugin, the shell requests the entry-point URL from `PluginRegistry` and loads it via `Qt.createComponent()`. This pattern appears throughout the plugin architecture, from service implementations to panel loaders:

```qml
// Example: Loading the Night Light service plugin
import QtQuick 2.15
import Quickshell 1.0

QtObject {
    Component.onCompleted: {
        const url = PluginRegistry.entryPointUrl(
            PluginRegistry.installedPlugins["omarchy.nightlight"], 
            "service"
        )
        if (url) {
            const comp = Qt.createComponent(url)
            if (comp.status === Component.Ready) {
                comp.createObject(null)
            } else {
                console.warn("Failed to load NightLight service:", comp.errorString())
            }
        }
    }
}

```

### Integration with shell.qml

The `shell.qml` file at lines 18-22 wires the registry into the application context by injecting `shellConfigProvider` and `shellConfigMutator` properties. This dependency injection allows `PluginRegistry` to read configuration state and persist changes via `setEnabled()` without directly handling filesystem I/O. When users enable or disable plugins, the registry updates [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) through the mutator and triggers a re-scan of the bar layout.

## Plugin Lifecycle and Events

The plugin system follows a strict lifecycle: **Scan**, **Enable/Disable**, and **Reload**. After startup, the registry emits `scanFinished` once both bundled and user directories are processed. Configuration changes trigger `setEnabled()` calls that modify [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) and refresh the active plugin set. If a developer modifies a plugin's manifest during runtime, the registry reloads the plugin and emits `pluginLoadFailed` on validation errors, providing immediate feedback without crashing the desktop.

## Summary

- **Discovery:** `PluginRegistry.qml` scans `firstPartyDir` and `~/.config/omarchy/plugins` for [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) files, validating them via `validateManifest()` at lines 43-90.
- **Security:** `isSafeEntryPoint()` and `entryPointUrl()` enforce directory sandboxing and prevent path traversal attacks.
- **Activation:** Plugins enable via [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) entries, with first-party plugins active by default unless listed in `disabledPlugins[]`.
- **Bar Widgets:** `BarWidgetRegistry.qml` manages widget registration with reactive revision tracking via `register()`.
- **Lifecycle:** Events like `scanFinished`, `pluginsChanged`, and `pluginLoadFailed` provide hooks for reactive UI updates.

## Frequently Asked Questions

### How does Omarchy validate plugin safety before loading?

Omarchy validates plugins through multiple layers in `PluginRegistry.qml`. The `validateManifest()` function checks schema compliance and safe ID strings, while `isSafeEntryPoint()` ensures all entry-point paths remain within the plugin directory. Additionally, `entryPointUrl()` performs runtime path resolution checks at lines 93-108 before returning `file://` URLs for component loading.

### Where should I place custom plugins for the Omarchy desktop?

User-installed plugins belong in `~/.config/omarchy/plugins`, scanned via the `pluginsDir` property in `PluginRegistry.qml`. First-party plugins bundled with the repository reside in a separate `firstPartyDir`. Both locations require a valid [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) with schema version 1 and properly defined entry points.

### How do I disable a built-in first-party plugin?

First-party plugins remain enabled by default unless explicitly disabled. Add the plugin's ID to the `disabledPlugins[]` array in your [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) configuration. The `isEnabled()` logic at lines 135-137 checks this array before implicitly activating bundled extensions.

### What is the difference between a service plugin and a bar widget?

Service plugins provide background functionality without UI elements, loaded via their `Service.qml` entry point and activated through the `plugins[]` array. Bar widgets are visual components that register through `BarWidgetRegistry.register()` at lines 16-25 and appear on the top bar via `bar.layout.*` configuration sections.