Skip to main content
Bar and launcher load third-party widgets as .so files. A plugin does not draw pixels. It emits Surface Description nodes through a small C ABI, and the host renders them. User-facing install, enable toggles, and the example plugins: Plugins. This page is the write-your-own side. otter-shell already ships otter-shell-plugins (examples) and otter-plugin-factory (opf). You can also install them on their own:

Layout and discovery

Packed plugins look like this:
Hosts search in this order. First match per id wins:
  1. OTTER_PLUGIN_PATH (colon-separated roots)
  2. $XDG_DATA_HOME/otter-shell/plugins
  3. ~/.local/share/otter-shell/plugins
  4. /usr/lib/otter-shell/plugins
  5. /usr/share/otter-shell/plugins
Bar and launcher layout names are always plugin:<id>. The bare id lives in plugin.conf. Enable gates live in ~/.config/otter-shell/plugins.conf as <id>_enabled = true|false. Hosts read that file before dlopen. Most plugins treat a missing key as enabled. The packaged snippet-provider is opt-in and defaults to disabled. Per-plugin files under ~/.config/otter-shell/plugins/<id>.conf are reserved for schemas with provides_settings = true. That settings UI is not in yet.

Manifest (plugin.conf)

Factory CLI (opf)

otter-plugin-factory is a separate package from the examples. After install, opf is on PATH. From a checkout, put bin/ on PATH instead.
Copy zig-out/plugins/<id>/ into ~/.local/share/otter-shell/plugins/ or set OTTER_PLUGIN_PATH. Factory docs/publishing.md covers the examples-package layout.

ABI v1

Plugins export C symbols. Zig plugins can @import a mirrored otter_plugin_abi module. ABI major is OTTER_PLUGIN_ABI_VERSION (1). Required exports: Bar, when provides_bar_widget is true:
  • otter_plugin_bar_attach returns an OtterPluginBarInstance with a vtable
  • Vtable: preferred_width, contribute, destroy, optional on_event
Launcher, when provider or SD-row flags are set:
  • otter_plugin_launcher_query / otter_plugin_launcher_activate
  • Optional otter_plugin_launcher_contribute_row for custom SD rows
Host API during contribute:
  • sd_rect / sd_label / sd_hit_button
  • Optional sd_text_input for IME-backed fields. Null-check on older hosts.
  • request_frame, log
BarVTable.on_event can receive click, hover, focus/blur, and text-input (buffer/preedit flags). Headers and Zig mirrors live in otter-bar, otter-launcher, and otter-shell-plugins (include/otter_plugin_abi.h, abi/abi.zig).

Examples in otter-shell-plugins

hello-label

Smallest SD chip that gets a real click from the host: bar_input_dispatch to the plugin slot to BarVTable.on_event(CLICK). Left click toggles the label text (Hello / Clicked) and background tint, then calls host.request_frame. No side-channel file.
Bar text field using host sd_text_input with Wayland text-input-v3. Focus the field, type, and confirm IME preedit/commit. Escape blurs. $XDG_RUNTIME_DIR/otter-bar-search is fallback only on old hosts.

Local development

ABI smoke, no Wayland:
Bar plugins: use otter-bar’s zig build -Doptimize=ReleaseFast plugin-abi-smoke -- <so>.

Trust

Installed .so plugins are fully trusted and share the host process. Ship examples through otter-shell-plugins, or load plugins you built yourself.