# How Omarchy's Plugin Architecture Works: A Technical Deep Dive

> Explore Omarchy's folder based plugin system. Learn how manifest.json files validate and load extensions dynamically into a registry for QML entry points.

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

---

**Omarchy implements a folder-based plugin system where each extension is defined by a [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) file, validated at startup, and loaded dynamically into a registry that maps QML entry points to specific UI kinds like bars, widgets, panels, and background services.**

Omarchy, the open-source desktop environment from Basecamp, treats every extension as a first-class plugin governed by a strict contract. The Omarchy plugin architecture enables third-party customization through a declarative manifest system that separates core shell logic from user-defined functionality. By leveraging runtime discovery and hot-reload capabilities, the system allows developers to iterate on QML-based components without restarting the desktop session.

## Core Plugin Concepts

The foundation of Omarchy's extensibility rests on two pillars: the **plugin manifest** and **kind-based entry points**. Every plugin operates as an isolated directory containing metadata and implementation files.

### Plugin Manifest Structure

Each plugin folder must contain a [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) file that declares the plugin's identity and capabilities. According to the source code in [`shell/plugins/bar/manifest.json`](https://github.com/basecamp/omarchy/blob/main/shell/plugins/bar/manifest.json), the manifest specifies:

- **ID**: A unique identifier (e.g., `omarchy.weather`)
- **Name**: Human-readable display name
- **Kinds**: An array defining which UI patterns the plugin implements
- **EntryPoints**: A mapping that connects each kind to its QML implementation file

The manifest also includes optional **UI metadata** such as `displayName`, `category`, and `settingsForm`, which the shell uses to render configuration interfaces.

### Supported Plugin Kinds

Omarchy recognizes four distinct plugin kinds, each corresponding to a specific QML file pattern:

- **`bar`**: Implements a full status bar via `Bar.qml`
- **`bar-widget`**: Provides a widget that attaches to existing bars via `BarWidget.qml`
- **`panel`**: Creates detachable overlay panels via `Panel.qml`
- **`service`**: Runs background processes without UI via `Service.qml`

As seen in the `weather` plugin manifest at [`shell/plugins/panels/weather/manifest.json`](https://github.com/basecamp/omarchy/blob/main/shell/plugins/panels/weather/manifest.json), a single plugin can declare multiple kinds, though each requires a matching entry point in the `entryPoints` object.

## Plugin Discovery and Validation

Omarchy employs a dual-path discovery mechanism that scans both built-in system plugins and user-space extensions.

### Directory Scanning

At startup, the shell recursively walks two directories:

1. `shell/plugins/` – Built-in plugins shipped with Omarchy
2. `~/.config/omarchy/plugins/` – User-installed third-party extensions

Every immediate subfolder containing a [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) is considered a plugin candidate. The test suite in [`plugins-test.sh`](https://github.com/basecamp/omarchy/blob/main/plugins-test.sh) enforces that every top-level folder under the plugins directory must include a valid manifest.

### Manifest Validation

Before registration, each candidate undergoes strict validation via the `omarchy-plugin-validate` routine. As documented in [`test/shell.d/plugin-validate-test.sh`](https://github.com/basecamp/omarchy/blob/main/test/shell.d/plugin-validate-test.sh), the validator performs the following checks:

- **ID Safety**: The `id` field must not contain `..` or `/` characters to prevent directory traversal
- **Kind Array**: The `kinds` property must be a non-empty array
- **Entry Point Existence**: Every declared kind must have a corresponding entry in `entryPoints`
- **Uniqueness**: No duplicate kind definitions are permitted within a single manifest

Any validation failure aborts the loading process for that specific plugin, ensuring the registry contains only well-formed extensions.

## Plugin Registration and Runtime

Once validated, plugins transition into the active runtime environment through an in-memory registry system.

### In-Memory Registry

The **plugin registry** maintains a live mapping of kinds to their QML entry points. It stores the file paths to implementation files (e.g., `BarWidget.qml`) and associated metadata. This registry drives the shell's IPC command set:

- `listPlugins`: Returns all registered plugins with their metadata
- `enablePlugin`: Activates a plugin in the registry
- `disablePlugin`: Deactivates a plugin without deleting its files

The contract between the registry and IPC layer is verified by [`test/shell.d/plugin-registry-contract-test.sh`](https://github.com/basecamp/omarchy/blob/main/test/shell.d/plugin-registry-contract-test.sh), ensuring consistent behavior across shell restarts.

### Hot-Reload Mechanism

Omarchy monitors all plugin directories for filesystem changes. When a developer modifies a QML file or [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) within `~/.config/omarchy/plugins/`, the shell automatically reloads that specific plugin. As noted in [`test/shell.d/runtime-smoke-test.sh`](https://github.com/basecamp/omarchy/blob/main/test/shell.d/runtime-smoke-test.sh), this hot-reload preserves application state where possible, enabling rapid development iteration without desktop restarts.

## CLI Integration and User Workflows

The Omarchy CLI provides three primary commands for plugin lifecycle management, implemented as wrappers around the registry API.

### Enabling and Disabling Plugins

Users control plugin activation through shell commands:

```bash

# Activate the weather widget

omarchy-plugin-enable omarchy.weather

# Deactivate the plugin

omarchy-plugin-disable omarchy.weather

```

These commands update the active registry and adjust the UI layout according to the manifest's default placement rules. The [`plugin-enable-test.sh`](https://github.com/basecamp/omarchy/blob/main/plugin-enable-test.sh) suite verifies that enabled plugins correctly register their entry points and appear in the `listPlugins` IPC response.

### Cloning Existing Plugins

The `omarchy-plugin-clone` command copies an installed plugin into the user configuration directory:

```bash
omarchy-plugin-clone omarchy.weather

```

This command, tested in [`plugin-clone-test.sh`](https://github.com/basecamp/omarchy/blob/main/plugin-clone-test.sh), preserves dependencies and metadata while creating an editable copy at `~/.config/omarchy/plugins/`, allowing users to customize built-in functionality without modifying system files.

## Building a Custom Plugin

Creating a custom extension requires three components: a directory, a manifest, and a QML implementation file.

### Directory Structure

Create a folder under the user plugins directory:

```

~/.config/omarchy/plugins/my.example/
├─ manifest.json
└─ BarWidget.qml

```

### Manifest Definition

Create [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) with the following structure:

```json
{
  "schemaVersion": 1,
  "id": "my.example",
  "name": "Example Widget",
  "version": "0.1.0",
  "author": "Me",
  "description": "A simple bar-widget example",
  "kinds": ["bar-widget"],
  "entryPoints": { "barWidget": "BarWidget.qml" },
  "barWidget": {
    "displayName": "Example",
    "category": "Demo",
    "allowMultiple": false
  }
}

```

### QML Implementation

Create `BarWidget.qml` containing the widget logic:

```qml
import QtQuick 2.15
import QtQuick.Controls 2.15

Item {
    width: 100; height: 24
    Text { anchors.centerIn: parent; text: "Hello!" }
}

```

### Activation

Enable the plugin via CLI:

```bash
omarchy-plugin-enable my.example

```

The shell reads the manifest, registers the `barWidget` entry point, and instantiates the QML component on the default bar.

## Summary

- **Folder-Based Architecture**: Every plugin is a directory containing [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) and QML implementation files discovered at `shell/plugins/` and `~/.config/omarchy/plugins/`.
- **Strict Validation**: The `omarchy-plugin-validate` routine enforces ID safety, kind existence, and entry point mapping before registration.
- **Kind System**: Plugins declare capabilities as `bar`, `bar-widget`, `panel`, or `service`, each mapping to specific QML file patterns.
- **Hot-Reload**: Filesystem watchers enable automatic reloading of plugins in `~/.config/omarchy/plugins/` without shell restarts.
- **CLI Management**: Commands like `omarchy-plugin-enable`, `omarchy-plugin-disable`, and `omarchy-plugin-clone` provide safe manipulation of the active registry.

## Frequently Asked Questions

### What file structure is required for an Omarchy plugin?

An Omarchy plugin requires a dedicated folder containing a [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) file at the root and QML implementation files referenced in the manifest's `entryPoints` object. The folder must reside in either `shell/plugins/` for system extensions or `~/.config/omarchy/plugins/` for user extensions. The manifest defines the plugin ID, supported kinds, and UI metadata, while the QML files provide the actual implementation for each declared capability.

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

Omarchy validates manifests through the `omarchy-plugin-validate` routine, which checks that the plugin ID contains no directory traversal characters (`..` or `/`), that the `kinds` array is non-empty, and that every kind has a matching entry point defined in `entryPoints`. The validator also ensures no duplicate kind definitions exist within a single manifest. Any validation failure prevents the plugin from entering the in-memory registry, protecting the shell from malformed extensions.

### Can I modify a plugin without restarting the Omarchy desktop?

Yes. Omarchy implements a hot-reload mechanism that watches all plugin directories for changes. When you modify a QML file or [`manifest.json`](https://github.com/basecamp/omarchy/blob/main/manifest.json) within a plugin folder, particularly in `~/.config/omarchy/plugins/`, the shell automatically reloads that specific plugin while preserving application state. This behavior is verified in [`test/shell.d/runtime-smoke-test.sh`](https://github.com/basecamp/omarchy/blob/main/test/shell.d/runtime-smoke-test.sh) and enables rapid development iteration without session restarts.

### What is the difference between `omarchy-plugin-enable` and `omarchy-plugin-clone`?

`omarchy-plugin-enable` activates a plugin by adding it to the in-memory registry and placing its UI components according to the manifest defaults, while `omarchy-plugin-clone` creates a copy of an existing plugin in your user configuration directory. Use `enable` to activate built-in or already-installed plugins, and use `clone` when you want to customize an existing plugin without modifying the original system files.