# Understanding the Quickshell Plugin System with Manifests and Kinds

> Explore Omarchy's Quickshell plugin system. Learn how JSON manifests define plugin capabilities, entry points, and kinds, validated by the PluginRegistry for seamless shell integration.

- Repository: [Omacom/omarchy](https://github.com/omacom/omarchy)
- Tags: deep-dive
- Published: 2026-09-13

---

**Omarchy's Quickshell UI uses a manifest-driven plugin architecture where JSON descriptors define plugin capabilities, entry points, and kinds, validated by the PluginRegistry service before exposure to the shell.**

Understanding the Quickshell plugin system with manifests and kinds is essential for extending Omarchy's desktop environment safely. The architecture separates first-party bundled extensions from user-installed third-party plugins through a rigorous validation pipeline implemented in `shell/services/PluginRegistry.qml`. Every plugin must declare its `kinds`—functional classifications that determine how the shell loads and renders the component.

## How the PluginRegistry Scans and Validates Manifests

The **PluginRegistry** service operates as the central authority for plugin discovery and validation. It scans two distinct locations during initialization:

- **First-party plugins** – Bundled with Omarchy and located in the `firstPartyDir` directory
- **Third-party plugins** – User-installed extensions under `~/.config/omarchy/plugins`

During a rescan operation, the registry launches a Bash script that emits each plugin's [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) as a discrete block of text. The `parseScanOutput` method (lines 76‑124 in `shell/services/PluginRegistry.qml`) receives this output, parses the JSON, and validates the structure.

### Required Manifest Fields

Validation guarantees that every manifest contains these required keys:

| Field | Description | Constraints |
|-------|-------------|-------------|
| `id` | Unique plugin identifier | Must not contain `/` or `..` characters |
| `name` | Human-readable title | Displayed in UI selectors |
| `version` | Semantic version string | Parsed for compatibility checks |
| `kinds` | Functional classification array | Determines valid entry points |
| `entryPoints` | Map of kind → relative file path | Path must stay within plugin directory |

### Optional Configuration Sections

Beyond required fields, manifests may include:

- **`barWidget`** – Specifies `defaultSection` as `"left"`, `"center"`, or `"right"` for automatic placement
- **`omarchy`** metadata – Declares **host capabilities** such as `"authentication"` for privileged operations

### Internal Metadata Stamping

After validation, the registry modifies the manifest map with two internal flags before adding it to `installedPlugins`:

- `__isFirstParty` – Boolean indicating bundled status
- `__hostCapabilities` – Inherited capabilities when third-party plugins clone first-party functionality

## Understanding Plugin Kinds and Entry Points

The `kinds` array classifies plugins by their functional role in the shell. This classification determines valid entry points and UI placement options.

### Supported Plugin Kinds

| Kind | Purpose | Typical Entry Point Key |
|------|---------|-------------------------|
| `bar` | Full bar configuration replacing the entire bar | `bar` |
| `bar-widget` | Widget living within the bar layout | `barWidget` |
| `panel` | Full-screen or tiled panel interface | `panel` |
| `service` | Background daemon or persistent service | `service` |
| `menu` | Top-level menu (often paired with bar-widgets) | `menu` |
| `idle` | Idle-time task or screensaver component | `idle` |

### Entry Point Resolution

The `entryPointUrl(manifest, kind)` function (lines 118‑132) constructs safe `file://` URLs by resolving the relative path from `entryPoints` against the plugin's source directory. The method `isSafeEntryPoint` (lines 36‑40) rejects absolute paths, parent directory references (`..`), and empty strings before resolution.

## Core Plugin Lifecycle Workflow

The Quickshell plugin system follows a five-stage pipeline:

1. **Rescan** – `PluginRegistry.rescan()` builds the discovery Bash script, executes it, and captures stdout containing manifest blocks
2. **Parse** – `parseScanOutput` splits the text into individual manifests, validates JSON structure, and populates the `installedPlugins` map
3. **Entry-point resolution** – `entryPointUrl()` constructs validated file URLs ensuring paths remain within the plugin sandbox
4. **Enable/disable** – `setEnabled(id, value, placement)` (lines 74‑86) updates [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json), manages the `plugins[]` and `disabledPlugins[]` arrays, and handles bar widget cloning logic via `moveBarEntry` and `barTarget` functions
5. **Shell consumption** – The central `shell.qml` module queries the registry through `barManifestFor(id)` (lines 194‑197) and `isBarOptionManifest(manifest)` (lines 199‑205) to load appropriate QML components

## Security and Sandbox Guarantees

Omarchy implements multiple safety layers to prevent plugin escalation:

- **Path safety** – `isSafeEntryPoint` validates that resolved paths remain within the plugin's source directory (lines 124‑131)
- **Namespace protection** – Third-party plugins cannot use IDs starting with `omarchy.`; the parser warns and discards such manifests (lines 30‑35)
- **URL validation** – `entryPointUrl` ensures returned URLs always begin with the plugin's root directory, returning empty strings for unsafe resolutions

These checks prevent malicious plugins from escaping their sandbox or hijacking built-in components through path traversal attacks.

## Practical Implementation Examples

### Creating a Minimal Bar Widget Manifest

Place this [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json) under `~/.config/omarchy/plugins/my.clock/manifest.json`:

```json
{
  "id": "my.clock",
  "name": "Clock Widget",
  "version": "1.0.0",
  "kinds": ["bar-widget"],
  "entryPoints": {
    "bar-widget": "Widget.qml"
  },
  "barWidget": {
    "defaultSection": "right"
  }
}

```

The registry exposes the widget at `file:///home/USER/.config/omarchy/plugins/my.clock/Widget.qml` after the next rescan.

### Enabling Plugins Programmatically

From within QML, enable a plugin and define its bar placement:

```qml
Component.onCompleted: {
    // Place widget before the system tray in the right section
    var placement = { before: "omarchy.tray", section: "right" };
    var success = shell.pluginRegistry.setEnabled("my.clock", true, placement);
    if (!success) console.error("Failed to enable my.clock");
}

```

Under the hood, `setEnabled` updates `config.bar.layout.right` and manages the active plugin registry.

### Resolving Entry Point URLs

Access plugin entry points safely through the registry API:

```javascript
var manifest = shell.pluginRegistry.installedPlugins["my.panel"];
var url = shell.pluginRegistry.entryPointUrl(manifest, "panel");
console.log("Panel source URL:", url);

```

This guarantees the URL points inside the plugin directory or returns an empty string if validation fails.

## Summary

- The **PluginRegistry** in `shell/services/PluginRegistry.qml` validates all manifests before exposing them to the shell
- Every plugin requires `id`, `name`, `version`, `kinds`, and `entryPoints` fields in [`manifest.json`](https://github.com/omacom/omarchy/blob/main/manifest.json)
- **Kinds** determine functional roles: `bar`, `bar-widget`, `panel`, `service`, `menu`, and `idle`
- **Entry points** are resolved through `entryPointUrl()` with path traversal protection via `isSafeEntryPoint()`
- Third-party plugins live in `~/.config/omarchy/plugins` and cannot use the `omarchy.` ID prefix
- The registry stamps manifests with `__isFirstParty` and `__hostCapabilities` for internal tracking

## Frequently Asked Questions

### What fields are required in a Quickshell manifest.json?

Every manifest must include `id` (unique identifier without `/` or `..`), `name` (display title), `version` (semantic version), `kinds` (array of functional types), and `entryPoints` (map of kind to relative file paths). Optional fields include `barWidget` for layout hints and `omarchy` for host capability declarations.

### How does Omarchy prevent malicious plugins from accessing system files?

The system employs `isSafeEntryPoint` (lines 36‑40) to reject absolute paths and parent directory references before resolution. The `entryPointUrl` function further validates that resolved absolute paths start with the plugin's source directory (lines 124‑131). Third-party plugins cannot impersonate system plugins by using IDs starting with `omarchy.`.

### Can third-party plugins override built-in Omarchy components?

No. The namespace protection in `parseScanOutput` (lines 30‑35) explicitly warns and discards any third-party manifest with an ID prefix of `omarchy.`. First-party plugins bundled in the `firstPartyDir` receive the `__isFirstParty` flag, ensuring privileged components remain distinct from user-installed extensions.

### What is the difference between bar and bar-widget kinds?

The `bar` kind represents a complete bar configuration that replaces the entire shell bar, using the `bar` entry point. The `bar-widget` kind represents individual widgets that live within the bar layout, using the `barWidget` entry point and supporting `defaultSection` placement in left, center, or right zones.