Develop plugins for DankMaterialShell (DMS), a QML-based Linux desktop shell built on Quickshell. Supports five plugin types: widget (bar + Control Center), daemon (background service), launcher (search + actions), desktop (draggable desktop widgets), and composite (multi-surface). Covers manifest creation, QML component development, startup checks, settings UI, data persistence, theme integration, PopoutService usage, IPC runtime discovery, and external command execution. Use when the user wants to create, modify, or debug a DMS plugin, or asks about the DMS plugin API.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Develop plugins for DankMaterialShell (DMS), a QML-based Linux desktop shell built on Quickshell. Supports five plugin types: widget (bar + Control Center), daemon (background service), launcher (search + actions), desktop (draggable desktop widgets), and composite (multi-surface). Covers manifest creation, QML component development, startup checks, settings UI, data persistence, theme integration, PopoutService usage, IPC runtime discovery, and external command execution. Use when the user wants to create, modify, or debug a DMS plugin, or asks about the DMS plugin API.
DMS plugins extend the desktop shell with custom widgets, background services, launcher
integrations, and desktop widgets. Plugins are QML components discovered from
~/.config/DankMaterialShell/plugins/.
Shows in the bar? - Use widget. Displays a pill in DankBar, optionally opens a popout,
optionally integrates with Control Center.
Runs in background only? - Use daemon. No visible UI, reacts to events (wallpaper
changes, notifications, battery level, etc.).
Provides searchable/actionable items? - Use launcher. Items appear in the DMS launcher
with trigger-based filtering (e.g., type = for calculator, : for emoji).
Shows on the desktop background? - Use desktop. Draggable, resizable widget on the
desktop layer.
Needs multiple surfaces? - Use composite. A single plugin that registers any combination
of the above (e.g., a daemon + bar widget + desktop widget). Each surface gets its own
QML component file.
{"id":"yourPlugin","name":"Your Plugin Name","description":"Brief description of what your plugin does","version":"1.0.0","author":"Your Name","type":"widget","capabilities":["your-capability"],"component":"./YourWidget.qml"}
For composite plugins, create a separate QML file per surface. Each surface uses the same
base component as the corresponding single-surface type (PluginComponent for widget/daemon,
Item for launcher, etc.). All surfaces share the same pluginId and pluginService.
MyCompositePlugin/
plugin.json
MyBarWidget.qml # PluginComponent (widget surface)
MyDaemon.qml # PluginComponent (daemon surface)
MyDesktopWidget.qml # Item with desktop widget properties
Settings.qml # Shared settings for all surfaces
Use pluginService.pluginHasSurface(pluginId, "widget") to check whether a specific surface
is registered for a plugin at runtime.
Step 4: Add Startup Check (Optional)
Gate plugin activation on dependency checks by providing a startupCheck component. This
runs before the plugin loads and blocks activation if a required tool or condition is missing.
Create a StartupCheck.qml (non-visual QtObject):
import QtQuick
import qs.Common
QtObject {
function check(done) {
Proc.runCommand("myPlugin.depCheck", ["sh", "-c", "command -v mytool"], (stdout, exitCode) => {
if (exitCode === 0) {
done(null);
return;
}
done({
"title": I18n.tr("mytool is required"),
"details": I18n.tr("Install 'mytool' and re-enable this plugin.")
});
});
}
}
The done callback accepts:
null - allow activation
A string - block with a short error message
{ title, details } - block with a title and expandable details body
A synchronous variant (no done parameter, return the result directly) is also supported.
Failed checks show a toast error and store the error in pluginService.pluginLoadErrors.
Run the shell with verbose output: qs -v -p $CONFIGPATH/quickshell/dms/shell.qml
Open Settings > Plugins > Scan for Plugins
Enable your plugin and add it to the DankBar layout
Runtime plugin discovery via IPC:
Plugins can be scanned, rescanned, and reloaded at runtime without restarting the shell:
dms ipc plugin-scan scan # Trigger a full rescan of all plugin directories
dms ipc plugin-scan rescan <id> # Force rescan of a specific plugin
dms ipc plugin-scan reload <id> # Force reload of a loaded plugin
dms ipc plugin-scan list # List all known plugins (TSV: id, loaded, type, name)
dms ipc plugin-scan status <id> # Get status of a specific plugin (TSV: loaded, type, error)
Plugin IDs are validated against ^[a-zA-Z0-9_\-:]{1,64}$.
Common issues:
Plugin not detected: check plugin.json syntax with jq . plugin.json
Widget not showing: ensure it's enabled AND added to a DankBar section
Settings error: verify settings_write permission is declared
Data not persisting: check pluginService injection and permissions
Startup check failing: check pluginService.pluginLoadErrors or run dms ipc plugin-scan status <id>
Common Mistakes
Missing settings_write permission - Settings UI shows error without it
Missing property var popoutService: null - Must declare for injection to work
Missing vertical bar pill - Widget disappears when bar is on left/right edge
Hardcoded colors - Use Theme.* properties, not hex values
Using globalThis.clipboard - Does not exist; use Quickshell.execDetached(["dms", "cl", "copy", text])
Wrong Theme property names - Theme.fontSizeS does not exist, use Theme.fontSizeSmall
Wrong import for Quickshell - Use import Quickshell (not import QtQuick for execDetached)
Forgetting categories in launcher items - Items won't display without it
Not handling null pluginService - Always use optional chaining or null checks
Using PluginComponent for launchers - Launchers use plain Item, not PluginComponent
Using requires instead of dependencies - requires is deprecated; use dependencies
Providing both component and components - Use one or the other, not both
Missing trigger on composite with launcher surface - Still required when components has a launcher key