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

> Learn to create Hyprland plugins as C++ shared libraries. This guide covers exporting hyprland_load and hyprland_unload functions to register callbacks and commands at runtime.

- Repository: [Hypr Development/Hyprland](https://github.com/hyprwm/Hyprland)
- Tags: how-to-guide
- Published: 2026-07-26

---

**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`](https://github.com/hyprwm/Hyprland/blob/main/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`](https://github.com/hyprwm/Hyprland/blob/main/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`](https://github.com/hyprwm/Hyprland/blob/main/CMakeLists.txt) fragment that links against Hyprland libraries:

```cmake
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:

```cpp
#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:

```cpp
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:

```cpp
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:

```cpp
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:

```cpp
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:

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

```

Restart the compositor to trigger the loading sequence. The [`Instance.cpp`](https://github.com/hyprwm/Hyprland/blob/main/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`](https://github.com/hyprwm/Hyprland/blob/main/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`](https://github.com/hyprwm/Hyprland/blob/main/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`](https://github.com/hyprwm/Hyprland/blob/main/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`](https://github.com/hyprwm/Hyprland/blob/main/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.