How to Create a Hyprland Plugin: Complete Guide to Building Loadable Modules

Hyprland plugins are C++ shared libraries (.so files) that export hyprland_load and optionally hyprland_unload functions, allowing developers to register signal callbacks and custom commands at runtime.

Creating a Hyprland plugin involves compiling a shared object that interfaces with the compositor's public API. According to the Hyprland source code, the core loading mechanism resides in src/start/src/core/Instance.cpp, which uses dlopen and dlsym to discover and initialize plugin entry points at startup.

Understanding the Hyprland Plugin Architecture

Hyprland extends its functionality through dynamically loadable modules discovered via configuration directives. When the compositor parses exec = <path>.so entries, it passes each path to the dynamic loader implemented in the Instance class.

The plugin system relies on two key components:

  • Entry point resolution: The compositor searches for the hyprland_load symbol using dlsym after opening the library with dlopen
  • Public API access: The src/includes.hpp header aggregates all major interfaces, including window management, monitors, workspaces, and animations

Plugins operate within the compositor's process space, receiving direct access to the running Hyprland::CInstance object through the load function's parameter.

Prerequisites and Build Setup

To compile a Hyprland plugin, you need the Hyprland development headers and a CMake configuration that targets shared library output.

Create a CMakeLists.txt fragment that links against Hyprland libraries:

add_library(myplugin SHARED myplugin.cpp)
target_include_directories(myplugin PRIVATE ${Hyprland_SOURCE_DIR}/src)
target_link_libraries(myplugin PRIVATE hyprutils hyprland)
set_target_properties(myplugin PROPERTIES PREFIX "")

The PREFIX "" property ensures the output file is named myplugin.so rather than libmyplugin.so, matching the extension Hyprland expects.

Include the main API header in your source:

#include "includes.hpp"  // Aggregates Hyprland core interfaces

Implementing the Plugin Entry Points

Every Hyprland plugin must implement at least one externally visible function: hyprland_load. Optionally, implement hyprland_unload for cleanup.

The hyprland_load Function

This function receives a pointer to the running instance and serves as your initialization hook:

extern "C" void hyprland_load(void* hyprlandInstance) {
    auto* pInstance = static_cast<Hyprland::CInstance*>(hyprlandInstance);
    
    // Plugin initialization logic here
    Hyprland::logger::info("Plugin loaded successfully");
}

Use extern "C" to prevent C++ name mangling, ensuring the compositor can resolve the symbol correctly.

Optional Cleanup with hyprland_unload

Implement this function to remove callbacks and free resources when the plugin is unloaded:

extern "C" void hyprland_unload() {
    // Remove signal connections and clean up heap allocations
    Hyprland::logger::info("Plugin unloading");
}

Registering Signals and Custom Commands

Once you have the instance pointer, you can subscribe to compositor events or expose new functionality.

Subscribing to Window Events

Connect to signals on the instance object to react to compositor state changes:

extern "C" void hyprland_load(void* instance) {
    auto* pInstance = static_cast<Hyprland::CInstance*>(instance);
    
    // Log when new windows are created
    pInstance->m_sSignalNewWindow.connect([](CWindow* w) {
        Hyprland::logger::info("New window created: {}", w->m_szTitle);
    });
    
    // React to monitor connections
    pInstance->m_sSignalMonitorAdded.connect([](CMonitor* m) {
        Hyprland::logger::info("Monitor added: {}", m->name);
    });
}

Exposing Custom Commands

Register commands that users can invoke via Hyprland's dispatch system:

Hyprland::registerCommand("myplugin:hello", []() {
    Hyprland::logger::info("Hello from myplugin!");
});

Users can then invoke this via hyprctl dispatch myplugin:hello.

Loading and Testing Your Plugin

After compiling to a .so file, configure Hyprland to load it at startup.

Add the plugin path to your Hyprland configuration file:

exec = $HOME/.config/hypr/plugins/myplugin.so

Restart the compositor to trigger the loading sequence. The Instance.cpp loader will dlopen the path and invoke your entry point automatically.

For a complete reference implementation, examine the test plugin at hyprtester/src/tests/misc/plugin.cpp in the official repository. This example demonstrates signal registration patterns and proper cleanup procedures used by the Hyprland test suite.

Summary

  • Hyprland plugins are shared libraries (.so) exposing hyprland_load and optionally hyprland_unload functions.
  • The loading mechanism in src/start/src/core/Instance.cpp uses dlopen and dlsym to resolve entry points from exec = path.so configuration entries.
  • API access requires including src/includes.hpp, which aggregates the public interfaces for windows, monitors, and workspaces.
  • Signal registration occurs through the CInstance pointer passed to hyprland_load, connecting lambdas to events like m_sSignalNewWindow.
  • Custom commands are registered via Hyprland::registerCommand(), making them available through hyprctl.

Frequently Asked Questions

What programming language are Hyprland plugins written in?

Hyprland plugins are written in C++. They must compile to native shared libraries (.so files) that expose C-linkage functions (extern "C") to ensure symbol names are not mangled and can be resolved by the dlsym call in the Hyprland loader.

Where does Hyprland look for plugin entry points?

Hyprland looks for the hyprland_load symbol in every shared library specified via exec = directives in the configuration. The symbol resolution happens in src/start/src/core/Instance.cpp using standard POSIX dynamic loading functions (dlopen and dlsym).

Can I unload a Hyprland plugin without restarting the compositor?

Hyprland supports the hyprland_unload optional entry point for cleanup, but removing a plugin from memory dynamically depends on the specific Hyprland version and configuration. Typically, you must restart the compositor to fully unload a plugin, though the unload function ensures proper cleanup of registered callbacks when the process terminates.

How do I debug a plugin that crashes Hyprland?

Build your plugin with debug symbols (-g flag) and run Hyprland with gdb or check the stderr logs. Ensure you validate all pointers received from the API (such as CWindow* or CMonitor*) before dereferencing them, as signal callbacks may fire during transient states where objects are partially constructed.

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 →