# How to Enable Third-Party Plugins in Omarchy's shell.json

> Easily enable third-party plugins in Omarchy by modifying your shell.json. Learn how to add descriptor objects to load QML components dynamically from the extensions directory.

- Repository: [37signals/omarchy](https://github.com/basecamp/omarchy)
- Tags: how-to-guide
- Published: 2026-08-24

---

**Third-party plugins are enabled by adding descriptor objects to the `plugins` array in `$OMARCHY_PATH/config/omarchy/shell.json`, which Quickshell reads at startup to dynamically load QML components from the `config/omarchy/extensions/` directory.**

Omarchy's desktop environment relies on Quickshell to render its interface, with all plugin registration centralized in a single JSON configuration file. Understanding how to modify [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) allows you to extend the desktop with custom widgets, indicators, and utilities without altering core source files. This guide walks through the exact steps to register external plugins, the structure of plugin descriptors, and how the loader in `shell/shell.qml` instantiates each component.

## How the Plugins Array Works in shell.json

At the root of Omarchy's configuration file, [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) contains a top-level **`plugins`** property that defaults to an empty array. This array acts as a manifest that tells Quickshell which external QML files to instantiate alongside the built-in UI elements.

According to the basecamp/omarchy source code, when the `plugins` array contains entries, the loader iterates over `shellConfig.plugins` during initialization. Each entry in the array represents a third-party component that will be loaded from the `config/omarchy/extensions/` directory hierarchy.

## Enabling a Third-Party Plugin: Step-by-Step

To activate a third-party plugin in Omarchy, you need to place the QML files in the correct location and register them in the configuration.

1. **Place the plugin files** into the `config/omarchy/extensions/` directory within your Omarchy installation path.

2. **Edit [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json)** and add a descriptor object to the `plugins` array. The object must include at least an `id` field that matches your plugin's QML component identifier.

3. **Save the configuration** and reload your Quickshell session. The watcher on [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) picks up changes automatically on the next start.

The loader uses `"$OMARCHY_PATH/config/omarchy/extensions"` as its base path, so plugins placed there can be referenced by filename alone.

## Plugin Descriptor Format

Each entry in the `plugins` array is a JSON object with specific keys that control loading behavior:

- **`id`** (required): The identifier used throughout the UI to reference the plugin (e.g., `omarchy.my.custom-plugin`).
- **`path`** (optional): The relative path to the QML file within the extensions directory. If omitted, the loader assumes the file is named `<id>.qml`.
- **`config`** (optional): A JSON object passed to the plugin's `Component.onCompleted` handler, allowing runtime customization without code changes.

A typical descriptor looks like this:

```json
{
  "id": "my.custom-plugin",
  "path": "my-plugin.qml",
  "config": {
    "refreshInterval": 60,
    "showIcon": true
  }
}

```

## How Quickshell Loads Plugins

The dynamic loading mechanism lives in `shell/shell.qml`, which implements the plugin instantiation logic. According to the basecamp/omarchy source code, the loader first reads [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) via `Qt.openUrlExternally("$OMARCHY_PATH/config/omarchy/shell.json")` and processes the `plugins` array during initialization.

For each entry in `shellConfig.plugins`, the loader builds a component using `Qt.createComponent("$OMARCHY_PATH/config/omarchy/extensions/" + entry.path)`—or assumes the filename is `<id>.qml` if `path` is omitted. The resulting instance is added to the main QML scene, exposing the plugin's exported properties and signals to Omarchy's bar, indicators, and other built-in modules.

This architecture ensures that third-party code runs in the same context as native UI elements while maintaining isolation through the QML component model.

## Practical Example: Adding a Custom Clock Plugin

Here is a complete workflow for adding a simple clock extension to Omarchy:

**Step 1:** Create `config/omarchy/extensions/extra-clock.qml` with the following content:

```qml
import QtQuick 2.15
import Quickshell 1.0

Item {
    Text {
        id: clock
        font.pixelSize: 24
        color: "white"
        text: Qt.formatDateTime(new Date(), "hh:mm:ss")
        Timer {
            interval: 1000; running: true; repeat: true
            onTriggered: clock.text = Qt.formatDateTime(new Date(), "hh:mm:ss")
        }
    }
}

```

**Step 2:** Add the descriptor to [`config/omarchy/shell.json`](https://github.com/basecamp/omarchy/blob/main/config/omarchy/shell.json):

```json
{
  "id": "omarchy.extra-clock",
  "path": "extra-clock.qml"
}

```

**Step 3:** Save the file and reload your Quickshell session. The `extra-clock` component will now instantiate alongside the default shell components.

## Summary

- **Configuration file**: Third-party plugins are registered in `$OMARCHY_PATH/config/omarchy/shell.json` via the `plugins` array.
- **Location**: Place plugin QML files in `config/omarchy/extensions/` to ensure the loader can resolve paths.
- **Descriptor requirements**: Each plugin needs at minimum an `id` field; optional `path` and `config` keys customize loading behavior.
- **Loading mechanism**: `shell/shell.qml` reads the array via `Qt.openUrlExternally()` and creates components dynamically using `Qt.createComponent()`.
- **Hot-reloading**: Changes to [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) take effect on the next Quickshell session start.

## Frequently Asked Questions

### What happens if the plugins array is empty?

When the `plugins` array is empty or omitted, Quickshell loads only the built-in UI elements defined in `shell/shell.qml`. The desktop environment runs with default functionality, and no third-party components are instantiated.

### Can I pass configuration data to a plugin without modifying its QML source?

Yes. Include a `config` object in the plugin descriptor within [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json). This JSON object is passed to the plugin's `Component.onCompleted` handler, allowing you to adjust behavior such as refresh intervals, display options, or API endpoints without touching the source code.

### Where should I place third-party plugin files in the Omarchy repository?

Place all third-party QML files and their associated resources (icons, scripts, etc.) in the `config/omarchy/extensions/` directory. The loader in `shell/shell.qml` uses this path as the base directory when resolving `Qt.createComponent()` calls, ensuring consistent module loading.

### Does Omarchy support hot-reloading of plugins without restarting the session?

While Quickshell watches [`shell.json`](https://github.com/basecamp/omarchy/blob/main/shell.json) for changes, new plugins are typically instantiated on the next session start or manual reload. The configuration file is monitored, but third-party QML components require a fresh component creation cycle initiated by the loader in `shell/shell.qml`.