# How the Omarchy Plugin Registry Handles Discovery, Loading, and Hot Reload

> Learn how the Omarchy plugin registry discovers, loads, and hot reloads plugins using manifest files, QML loaders, and file system watchers for seamless development.

- Repository: [Omacom/omarchy](https://github.com/omacom/omarchy)
- Tags: internals
- Published: 2026-09-10

---

**The Omarchy plugin registry discovers plugins by scanning the `shell/plugins/` directory for valid [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) files, dynamically loads them via QML loaders with injected configuration properties, and supports hot reloading through file system watchers that unload and re-instantiate components when source files change.**

Omarchy is an open-source desktop shell built on Quickshell that uses a modular plugin architecture to manage panels, overlays, and system menus. The **Omarchy plugin registry**, implemented in `shell/services/PluginRegistry.qml`, serves as the central orchestrator that validates, instantiates, and manages the lifecycle of all shell extensions without requiring a full session restart.

## Plugin Discovery and Validation

The registry begins its lifecycle by scanning the filesystem for valid plugin bundles.

### Directory Scanning and Manifest Validation

According to the source code in `shell/services/PluginRegistry.qml`, the registry recursively scans the `shell/plugins/` directory (and any additional paths defined in [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json)) for subdirectories containing a [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) file. For each discovered folder, the registry performs three validation steps:

- **Manifest parsing** – Reads the JSON to extract the plugin `name`, `type` (panel, overlay, agent, or menu), and `entry` QML component path.
- **File existence check** – Verifies that the `entry` QML file exists inside the plugin directory before attempting to load it.
- **Enablement verification** – Checks the user’s `~/.config/omarchy/shell.json` to determine if the plugin is marked as `enabled`. Disabled plugins are tracked but excluded from the active set.

Only plugins passing all three checks are passed to the loading phase.

## Dynamic Loading Architecture

Once validated, plugins are instantiated through a dynamic component system that isolates each extension in its own runtime context.

### Component Instantiation via QML Loaders

For each valid plugin, the registry creates a `Loader` element. The loader’s `sourceComponent` property is bound to the plugin’s entry QML file (for example, `Panel.qml` for panel-type plugins). This is exposed through the `listPlugins` property of the registry, which returns a JSON array of loaded plugin metadata sorted alphabetically.

When the loader instantiates, the QML component compiles and executes its `Component.onCompleted` hook, allowing the plugin to register its UI elements with the shell containers.

### Configuration Injection

The registry injects per-plugin settings from `~/.config/omarchy/shell.json` directly into each loader via property bindings. This enables runtime configuration of parameters such as `position`, `shortcut`, or custom `enabled` states without modifying the plugin source code. Each plugin receives its configuration subset as context properties before `Component.onCompleted` runs.

## Hot Reload Implementation

Omarchy supports iterative development through a file-watching mechanism that refreshes plugins without restarting the shell session.

### File System Watching

The registry utilizes Quickshell’s built-in `FileWatcher` QML type to monitor every plugin directory. When any file within a watched directory changes—whether the QML source, [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json), or auxiliary resources—the watcher emits a `fileChanged` signal captured by `shell/services/PluginRegistry.qml`.

### Reload Sequence

Upon detecting a change, the registry executes a deterministic unload-reload sequence:

1. **Unload** – Sets `loader.sourceComponent = undefined` to destroy the existing component instance and free associated resources.
2. **Re-validation** – Re-reads the [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) to capture any metadata updates, renamed entry files, or type changes.
3. **Re-instantiate** – Re-assigns `loader.sourceComponent` to the refreshed QML file, triggering recompilation and initialization.

Because each plugin is isolated within its own `Loader`, hot reloading affects only the targeted plugin. Transient UI state (such as open dropdowns or scroll positions) is discarded during reload, which aligns with the "refresh-and-reset" development workflow.

### Manual Reload Trigger

Developers can force a full registry refresh by running the `omarchy-reload-plugins` command located in `bin/`. This utility signals the shell to re-evaluate all plugin directories immediately, bypassing the file watcher.

## Creating a Compatible Plugin

To integrate with the registry, a plugin must follow the directory structure and metadata format expected by the discovery logic.

```json
// shell/plugins/panels/example/manifest.json
{
  "name": "example",
  "type": "panel",
  "entry": "Panel.qml",
  "enabled": true
}

```

```qml
// shell/plugins/panels/example/Panel.qml
import QtQuick 2.15
import Quickshell 0.1

Item {
    id: root
    width: 200; height: 40
    Text { text: "Example Panel"; anchors.centerIn: parent }
}

```

After placing these files in `shell/plugins/panels/example/`, the registry discovers the plugin on the next shell start. If you edit `Panel.qml` while the shell is running, the `FileWatcher` triggers an immediate hot reload, refreshing the panel UI without disturbing other components.

## Summary

- **Discovery** – The registry scans `shell/plugins/` for directories containing [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json), validates the entry QML exists, and checks enablement status in `~/.config/omarchy/shell.json`.
- **Loading** – Valid plugins are loaded via isolated `Loader` elements with configuration injected through property bindings, exposing a `listPlugins` property for shell consumption.
- **Hot Reload** – `FileWatcher` monitors plugin directories; on change, the registry unloads (`sourceComponent = undefined`) and re-instantiates the component to reflect updates immediately.
- **Manual Control** – The `bin/omarchy-reload-plugins` command forces a full registry refresh outside of the file-watching mechanism.

## Frequently Asked Questions

### How does the Omarchy plugin registry locate new plugins?

The registry scans the `shell/plugins/` directory (and additional paths specified in [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json)) for subdirectories containing a [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) file. It validates that the manifest’s `entry` field points to an existing QML file and that the plugin is enabled in the user configuration file `~/.config/omarchy/shell.json`.

### What happens to UI state when a plugin hot reloads?

Transient UI state is discarded because the reload sequence sets `loader.sourceComponent = undefined`, which destroys the old component instance entirely. The new instance starts fresh with default property values and re-executes `Component.onCompleted`. This behavior is intentional for development workflows where a clean state is preferred.

### Can plugins be disabled without removing their files from the system?

Yes. Users can set `enabled: false` for the specific plugin entry in `~/.config/omarchy/shell.json`. The registry tracks disabled plugins in its internal state but excludes them from the `listPlugins` property, preventing instantiation without deleting the plugin directory from `shell/plugins/`.

### How do I manually trigger a plugin reload if the file watcher fails?

Execute the `omarchy-reload-plugins` command found in the `bin/` directory of the Omarchy repository. This CLI wrapper sends a signal to the running shell instance, forcing `shell/services/PluginRegistry.qml` to re-scan all plugin directories and reload enabled extensions regardless of file system events.