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

Quickshell discovers JavaScript modules in shell/plugins/ at startup, registers their exported definitions against IDs defined in shell.json, and integrates them into the desktop via 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 requires 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 configuration through unique id fields, creating a registry of available desktop extensions.

Configuration Mapping

Each plugin entry in shell.json contains an id that maps to the corresponding module path. When the desktop initializes, the central 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, 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.

// 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 configuration:

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

When Quickshell reloads the configuration, BarModel.js calls entryId → “example”, loads 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:

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 using unique identifiers that 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 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 configuration file maps plugin IDs to panel positions (left, center, right). 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 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) 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →