# How to Create a Custom Omarchy Shell Plugin Using manifest.json

> Learn to create a custom Omarchy shell plugin using manifest.json. Effortlessly add functionality to your shell without code registration.

- Repository: [Omacom/omarchy](https://github.com/omacom/omarchy)
- Tags: how-to-guide
- Published: 2026-09-11

---

**Omarchy discovers and loads shell extensions by reading a [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) file placed in a plugin directory under `shell/plugins/`, automatically registering the plugin without requiring code-side registration.**

Omarchy is an open-source desktop environment that extends its functionality through a dynamic plugin system. By placing a properly structured [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) alongside your QML implementation files, you can add custom panels, background services, or widgets to the shell runtime. This guide walks through the exact schema, file placement, and reload mechanisms used by the Omarchy shell loader.

## Understanding the manifest.json Schema

The [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) file serves as the contract between your plugin and the Omarchy shell runtime (`shell.qml`). When the shell initializes or reloads, it scans subdirectories under `shell/plugins/` for these manifest files and parses them to determine which QML entry points to load.

### Required Fields

Every valid manifest must include these top-level keys:

- **`schemaVersion`** – Currently set to `1`. This versioning allows the shell loader to handle breaking changes in future Omarchy releases.
- **`id`** – A unique dot-separated identifier, conventionally prefixed with `omarchy.` (e.g., `omarchy.mycustompanel`). This ID must not conflict with existing plugins.
- **`name`** – Human-readable display name shown in plugin listings.
- **`version`** – Semantic version string (e.g., `0.1.0`).
- **`author`** – Creator attribution.
- **`description`** – Brief summary of functionality.
- **`kinds`** – An array of strings describing the plugin type(s). Valid values include `"service"` (background daemon), `"panel"` (UI bar element), and `"widget"` (standalone launchable component).
- **`entryPoints`** – An object mapping each kind to its corresponding QML filename. For example, `"panel": "Panel.qml"` tells the loader to instantiate `Panel.qml` when registering a panel-type plugin.

## Step-by-Step Implementation

Creating a functional plugin requires three components: a directory, a manifest, and a QML entry point. The shell's plugin discovery mechanism handles the rest.

### Create the Plugin Directory

Navigate to the appropriate subdirectory under `shell/plugins/` based on your plugin type. For a new panel, create a folder under `shell/plugins/panels/`:

```bash
mkdir -p shell/plugins/panels/my-awesome-panel/

```

Omarchy organizes plugins by category, though the loader will recognize manifests regardless of nested depth. Existing plugins like the Night Light service reside in `shell/plugins/services/nightlight/`, while the Wi-Fi QR panel lives in `shell/plugins/panels/wifiqr/`.

### Define the manifest.json

Create a [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) file in your plugin root with the required schema. The manifest at [`shell/plugins/services/nightlight/manifest.json`](https://github.com/omacom/omarchy/blob/main/shell/plugins/services/nightlight/manifest.json) demonstrates a service-type plugin, while [`shell/plugins/panels/wifiqr/manifest.json`](https://github.com/omacom/omarchy/blob/main/shell/plugins/panels/wifiqr/manifest.json) shows a panel implementation.

Here is a minimal manifest for a custom panel:

```json
{
  "schemaVersion": 1,
  "id": "omarchy.myawesomepanel",
  "name": "My Awesome Panel",
  "version": "0.1.0",
  "author": "Your Name",
  "description": "A custom panel showing whatever you like.",
  "kinds": [ "panel" ],
  "entryPoints": {
    "panel": "Panel.qml"
  }
}

```

The `entryPoints` object keys must match the values declared in `kinds`. If you specify multiple kinds, each requires a corresponding entry in `entryPoints` pointing to its QML implementation file.

### Implement the QML Entry Point

Create the QML file referenced in your manifest. This file must exist in the same directory as [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json). The entry point acts as the root component that the shell instantiates when loading your plugin.

For a panel plugin, implement a standard QtQuick `Item` or `Rectangle`:

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

Item {
    width: 200; height: 40
    Text {
        anchors.centerIn: parent
        text: "Hello Omarchy!"
        color: "#FFFFFF"
    }
}

```

Save this as `Panel.qml` alongside your manifest. For service-type plugins, follow the pattern in `shell/plugins/services/nightlight/Service.qml`, which implements background logic without visual components. Panel plugins should reference the structure in `shell/plugins/panels/wifiqr/Panel.qml`.

### Reload the Shell

Once your files are in place, trigger a plugin reload so the shell runtime recognizes the new manifest. The shell exposes this functionality through the `reloadPlugins()` function defined in `shell/shell.qml` (around line 1441).

Execute the reload command from a terminal:

```bash
omarchy-reload-shell

```

Alternatively, any action that triggers `shell.reloadPlugins()` will refresh the registry. Upon reload, the shell parses your [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json), validates the schema, and instantiates the QML components specified in `entryPoints`.

## Key Source Files and Architecture

Understanding the loader implementation helps debug registration issues. The core plugin discovery logic resides in `shell/shell.qml`, specifically within the `loadPluginWidget` and `reloadPlugins` functions. These methods iterate through `shell/plugins/`, parse JSON manifests, and register valid plugins with the shell's component factory.

Reference implementations provide working templates:

- **Service Example**: [`shell/plugins/services/nightlight/manifest.json`](https://github.com/omacom/omarchy/blob/main/shell/plugins/services/nightlight/manifest.json) and `Service.qml` demonstrate background service architecture.
- **Panel Example**: [`shell/plugins/panels/wifiqr/manifest.json`](https://github.com/omacom/omarchy/blob/main/shell/plugins/panels/wifiqr/manifest.json) and `Panel.qml` show UI panel integration.
- **Loader Logic**: `shell/shell.qml` contains the `reloadPlugins()` implementation that drives the discovery process.

## Summary

- Omarchy uses a declarative [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) system located in `shell/plugins/` subdirectories to discover extensions automatically.
- The manifest requires `schemaVersion`, `id`, `kinds`, and `entryPoints` to map plugin types to their QML implementations.
- Valid `kinds` include `"service"`, `"panel"`, and `"widget"`, each requiring a corresponding QML file in the `entryPoints` object.
- Place QML entry points in the same directory as [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json), matching the filenames specified in the manifest.
- Trigger registration by running `omarchy-reload-shell`, which invokes `reloadPlugins()` in `shell/shell.qml`.

## Frequently Asked Questions

### What happens if my manifest.json is malformed?

The shell loader in `shell/shell.qml` validates manifests during the `reloadPlugins()` scan. If required fields like `schemaVersion`, `id`, or `entryPoints` are missing or invalid, the plugin will be skipped without crashing the shell, though it won't appear in `omarchy-list-plugins` output. Check the shell logs for JSON parsing errors.

### Can a single plugin have multiple kinds?

Yes. Define multiple values in the `kinds` array (e.g., `["service", "panel"]`) and provide corresponding entry points in the `entryPoints` object. Each kind maps to a separate QML file that the shell instantiates appropriately, allowing hybrid plugins that provide both background logic and UI components.

### Where should I place plugin assets like icons?

Store auxiliary files—icons, images, or helper scripts—in your plugin directory alongside [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) and your QML files. Reference them using relative paths in your QML code (e.g., `"./icon.png"`). The shell does not restrict asset placement within the plugin folder, but keeping assets colocated with their manifest ensures portability.

### How do I uninstall a custom plugin?

Simply delete the plugin directory from `shell/plugins/` and run `omarchy-reload-shell`. The `reloadPlugins()` function will remove the plugin from the active registry since the manifest is no longer present on disk. No additional cleanup steps are required in the Omarchy configuration.