# How Shell Plugins in `shell/plugins/` Extend the Quickshell Desktop

> Discover how Quickshell desktop integrates shell plugins from shell/plugins/ using JavaScript modules and shell.json. Extend functionality without core code changes.

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

---

**Quickshell discovers JavaScript modules in `shell/plugins/` at startup, registers their exported definitions against IDs defined in [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json), and integrates them into the desktop via [`BarModel.js`](https://github.com/omacom/omarchy/blob/main/BarModel.js) without requiring core code changes.**

The omarch/omarchy repository implements a data‑driven plugin architecture that allows developers to extend the Quickshell desktop by dropping JavaScript files into the `shell/plugins/` directory. This modular system scans, loads, and merges plugin definitions with user configuration, enabling custom panel widgets, system services, and notifications to appear instantly in the desktop UI.

## Plugin Discovery and Configuration Binding

### Automatic Module Scanning

At startup, the core plugin loader scans `shell/plugins/` and `require`s each `.js` file, registering the exported object under a semantic name that matches the plugin’s purpose (e.g., panel widget, service, or OSD). The loader matches these modules to entries in the user’s [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json) configuration through unique **id** fields, creating a registry of available desktop extensions.

### Configuration Mapping

Each plugin entry in [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json) contains an **id** that maps to the corresponding module path. When the desktop initializes, the central [`shell/plugins/bar/BarModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/bar/BarModel.js) reconciles these IDs with the loaded plugin objects, determining which widgets render in which screen slots based on the exported configuration.

## The BarModel.js Integration Layer

Located at [`shell/plugins/bar/BarModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/bar/BarModel.js), this file provides the glue between raw plugin exports and the live desktop UI, handling lifecycle management and dynamic layout updates.

### Entry Handling and Settings Extraction

The model exposes functions such as `entryId`, `entrySettings`, `customModuleType`, and `customModulePath` to extract the plugin ID and its runtime parameters from configuration objects. These utilities standardize how the bar consumes heterogeneous plugin definitions, ensuring that `entrySettings` returns a shallow copy of configuration data (excluding the id) that the rendering engine expects.

### Dynamic Layout Computation

The `inlineSettingsDelta` function computes whether a configuration change affects only widget settings—allowing a live update—or requires a full bar rebuild. This optimizes performance by avoiding unnecessary UI reconstruction when users modify properties like labels or icons.

### Slot Resolution

Utilities like `pickDrawnSlot` and `pickPanelSlot` determine which visual slot on which monitor receives user interactions, handling panel hotkeys and popup anchoring for multi-monitor setups. These functions ensure that plugin widgets appear in the correct spatial context regardless of display configuration.

## Creating a Custom Panel Widget Plugin

To add functionality, create a JavaScript file under `shell/plugins/panels/<name>/Model.js` that exports a structured object conforming to the plugin contract recognized by [`BarModel.js`](https://github.com/omacom/omarchy/blob/main/BarModel.js).

```js
// File: shell/plugins/panels/example/Model.js
function entrySettings(entry) {
  // Return a shallow copy without the id – the bar model expects this shape
  return { label: entry.label || "Example", icon: entry.icon || "star" };
}
module.exports = {
  // The exported object is read by the core loader
  // `type` tells the bar that this is a panel widget
  type: "widget",
  // Optional QML source for a custom UI; omitted here uses the default widget UI
  source: "",    
  // Helper used by BarModel to read settings
  entrySettings,
};

```

Reference the plugin in your [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json) configuration:

```json
{
  "panels": {
    "left": [
      "omarchy.tray",
      { "id": "example", "label": "My Widget", "icon": "coffee" }
    ]
  }
}

```

When Quickshell reloads the configuration, [`BarModel.js`](https://github.com/omacom/omarchy/blob/main/BarModel.js) calls `entryId` → “example”, loads [`shell/plugins/panels/example/Model.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/panels/example/Model.js), and renders the widget with the provided label and icon. Any subsequent change to the entry’s settings is handled by `inlineSettingsDelta`, allowing an instant UI update without rebuilding the whole bar.

## Built-in Plugin Reference Implementations

The repository contains several canonical implementations demonstrating the plugin contract:

- **[`shell/plugins/panels/audio/Model.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/panels/audio/Model.js)**: Implements a built-in panel widget with type definitions and entry handling.
- **[`shell/plugins/services/battery/BatteryModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/services/battery/BatteryModel.js)**: Service-style plugin supplying dynamic battery status data to the bar.
- **[`shell/plugins/notifications/NotificationLogic.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/notifications/NotificationLogic.js)**: Integrates with the system notification server.
- **[`shell/plugins/menu/MenuModel.js`](https://github.com/omacom/omarchy/blob/main/shell/plugins/menu/MenuModel.js)**: Exposes complex logic for the Omarchy application menu.
- **[`shell/services/AppSearch.js`](https://github.com/omacom/omarchy/blob/main/shell/services/AppSearch.js)**: Demonstrates cross-plugin communication as a shared service that other plugins import.

## Summary

- **Drop-in architecture**: Place JavaScript files in `shell/plugins/` to register new capabilities; the loader discovers them automatically at startup.
- **ID-based binding**: Map plugins to UI positions via [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json) using unique identifiers that [`BarModel.js`](https://github.com/omacom/omarchy/blob/main/BarModel.js) resolves.
- **Entry handling**: Functions like `entrySettings` and `entryId` standardize configuration consumption across heterogeneous plugins.
- **Live updates**: `inlineSettingsDelta` distinguishes between settings changes requiring redraw versus full rebuild, optimizing performance.
- **Slot resolution**: `pickDrawnSlot` and `pickPanelSlot` manage multi-monitor placement and interaction routing without plugin awareness of display topology.

## Frequently Asked Questions

### What file structure is required for a new Quickshell plugin?

Create a directory under `shell/plugins/` corresponding to the plugin category (e.g., `panels/`, `services/`), then add a [`Model.js`](https://github.com/omacom/omarchy/blob/main/Model.js) file that exports an object with a `type` property and helper functions like `entrySettings`. The core loader recursively scans these paths and registers any valid JavaScript module.

### How does Quickshell know where to display my plugin?

The [`shell.json`](https://github.com/omacom/omarchy/blob/main/shell.json) configuration file maps plugin IDs to panel positions (left, center, right). [`BarModel.js`](https://github.com/omacom/omarchy/blob/main/BarModel.js) resolves these IDs and uses `pickPanelSlot` to determine the exact monitor and visual slot for rendering based on the current display layout.

### Can I update a plugin's settings without restarting the desktop?

Yes. [`BarModel.js`](https://github.com/omacom/omarchy/blob/main/BarModel.js) implements `inlineSettingsDelta` to detect whether a configuration change only affects widget properties versus structural changes. When only settings change, the UI updates instantly without rebuilding the entire bar, allowing real-time customization.

### How do plugins communicate with each other?

Plugins can import shared services from `shell/services/` (such as [`AppSearch.js`](https://github.com/omacom/omarchy/blob/main/AppSearch.js)) and expose their own functions via `module.exports`. The core loader manages these dependencies, creating a dependency injection pattern where services provide dynamic data that panel widgets consume.