How to Implement the PowertoyModuleIface Interface for a New PowerToys Utility
To implement the PowertoyModuleIface interface for a new PowerToys utility, create a DLL that exports powertoy_create(), derive a class from the abstract interface defined in src/modules/interface/powertoy_module_interface.h, and implement the mandatory life-cycle methods including enable(), disable(), and get_config().
PowerToys utilities are dynamically loaded as DLLs by the Runner process at startup. Each module must expose a C-linkage factory function that instantiates an object implementing the PowertoyModuleIface abstract base class, enabling the Runner to manage the utility's lifecycle, settings, and hotkey registration through a standardized API.
Understanding the PowertoyModuleIface Interface
Core Interface Location and Purpose
The PowertoyModuleIface interface is defined in src/modules/interface/powertoy_module_interface.h. This header establishes the contract between the PowerToys Runner and individual utilities. According to the Microsoft PowerToys source code, the Runner interacts with modules exclusively through this interface, treating each utility as a black box that responds to standard enable, disable, and configuration commands.
Mandatory Methods to Implement
Every class deriving from PowertoyModuleIface must override these pure-virtual methods:
get_name()– Returns the human-readable module name displayed in the Settings UIget_key()– Returns a unique identifier used as the JSON key for settings storageget_config()– Serializes the module's configuration schema to JSON for the Settings UIset_config()– Deserializes JSON from the Settings UI and persists values to diskenable()– Called when the user activates the utility; start background threads or hooks heredisable()– Called when the user deactivates the utility; release resources hereis_enabled()– Returns the current runtime enabled statedestroy()– Self-deletion hook called by the Runner during shutdown
Step-by-Step Implementation Guide
Create the DLL Project Structure
Microsoft provides a ModuleTemplate in tools/project_template/ModuleTemplate/ that preconfigures the correct project settings, export definitions, and folder layout. Use this template to ensure your DLL compiles with the proper runtime settings and linker options compatible with the PowerToys Runner.
The template includes boilerplate for registering an ETW trace provider in DllMain(), which allows your module to emit structured logs that PowerToys aggregates:
BOOL APIENTRY DllMain(HMODULE, DWORD reason, LPVOID) {
if (reason == DLL_PROCESS_ATTACH) Trace::RegisterProvider();
if (reason == DLL_PROCESS_DETACH) Trace::UnregisterProvider();
return TRUE;
}
Implement the Factory Function
The Runner locates your module by looking up the powertoy_create symbol via GetProcAddress. You must export this function with C linkage to prevent name mangling:
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() {
return new MyNewUtility(); // MyNewUtility derives from PowertoyModuleIface
}
This factory is the sole entry point. The Runner calls it once during startup to obtain the module instance, then manages the object through the PowertoyModuleIface pointer.
Handle Settings Persistence
Use the PowerToysSettings helper classes from src/common/SettingsAPI/settings_objects.h to guarantee a JSON-based schema that the Settings UI can render. Implement get_config() to describe your configurable properties:
virtual bool get_config(wchar_t* buffer, int* buffer_size) override {
HINSTANCE hinst = reinterpret_cast<HINSTANCE>(&__ImageBase);
PowerToysSettings::Settings settings(hinst, get_name());
settings.add_bool_toggle(L"enabled", L"Enable feature", g_settings.enabled);
settings.add_int_spinner(L"interval", L"Refresh interval", g_settings.interval, 1, 60, 1);
return settings.serialize_to_buffer(buffer, buffer_size);
}
Implement set_config() to parse incoming JSON and persist values:
virtual void set_config(const wchar_t* config) override {
try {
auto values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
if (auto v = values.get_bool_value(L"enabled")) g_settings.enabled = *v;
if (auto v = values.get_int_value(L"interval")) g_settings.interval = *v;
values.save_to_settings_file(); // Writes to %LOCALAPPDATA%\PowerToys\
} catch (...) { /* ignore malformed JSON */ }
}
Add Hotkey Support (Optional)
To expose system-wide hotkeys that the Runner registers on your behalf, override get_hotkeys() and on_hotkey():
virtual size_t get_hotkeys(Hotkey* buffer, size_t buffer_sz) override {
if (buffer && buffer_sz > 0) {
buffer[0] = { .win = true, .ctrl = false, .shift = true, .alt = false,
.key = 'U', .id = 0, .isShown = true }; // Win+Shift+U
}
return 1; // Number of hotkeys defined
}
virtual bool on_hotkey(size_t hotkeyId) override {
if (hotkeyId == 0) {
// Execute action
return true; // Swallow the keypress
}
return false;
}
Alternatively, implement GetHotkeyEx() and OnHotkeyEx() for extended registration options.
Complete Code Example
The following trimmed implementation from tools/project_template/ModuleTemplate/dllmain.cpp demonstrates the essential structure:
#include <interface/powertoy_module_interface.h>
#include <common/SettingsAPI/settings_objects.h>
const static wchar_t* MODULE_NAME = L"MyNewUtility";
struct ModuleSettings {
bool enabled = true;
int refresh_interval = 5;
} g_settings;
class MyNewUtility : public PowertoyModuleIface {
bool m_enabled = false;
void init_settings() {
try {
auto values = PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
if (auto v = values.get_bool_value(L"enabled")) g_settings.enabled = *v;
if (auto v = values.get_int_value(L"refresh_interval")) g_settings.refresh_interval = *v;
} catch (...) {}
}
public:
MyNewUtility() { init_settings(); }
virtual const wchar_t* get_name() override { return MODULE_NAME; }
virtual const wchar_t* get_key() override { return MODULE_NAME; }
virtual void destroy() override { delete this; }
virtual bool get_config(wchar_t* buffer, int* buffer_size) override {
PowerToysSettings::Settings settings(reinterpret_cast<HINSTANCE>(&__ImageBase), get_name());
settings.add_bool_toggle(L"enabled", L"Enable", g_settings.enabled);
settings.add_int_spinner(L"refresh_interval", L"Interval (sec)", g_settings.refresh_interval, 1, 60, 1);
return settings.serialize_to_buffer(buffer, buffer_size);
}
virtual void set_config(const wchar_t* config) override {
auto values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
if (auto v = values.get_bool_value(L"enabled")) g_settings.enabled = *v;
if (auto v = values.get_int_value(L"refresh_interval")) g_settings.refresh_interval = *v;
values.save_to_settings_file();
}
virtual void enable() override { m_enabled = true; }
virtual void disable() override { m_enabled = false; }
virtual bool is_enabled() const override { return m_enabled; }
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() {
return new MyNewUtility();
}
Registering Your Module with the Runner
After building your DLL, add the filename to the Runner's loading list in src/runner/main.cpp. The Runner iterates through this list during startup, calling LoadLibrary and powertoy_create() for each entry. Without this registration step, your module will not be instantiated even if the DLL is present in the installation directory.
Summary
- Implement
PowertoyModuleIfaceby deriving from the abstract base class insrc/modules/interface/powertoy_module_interface.h - Export
powertoy_create()as a C-linkage factory function returning your implementation - Override mandatory methods:
get_name(),get_key(),get_config(),set_config(),enable(),disable(),is_enabled(), anddestroy() - Use
PowerToysSettingshelpers fromsrc/common/SettingsAPI/settings_objects.hfor JSON schema generation and persistence - Optionally implement hotkey methods (
get_hotkeys(),on_hotkey()) to receive global shortcut events - Register your DLL filename in
src/runner/main.cppso the Runner loads it at startup
Frequently Asked Questions
What is the minimum set of methods required to implement PowertoyModuleIface?
You must implement seven pure-virtual methods: get_name(), get_key(), get_config(), set_config(), enable(), disable(), and destroy(). Additionally, you must implement is_enabled() to track runtime state. These methods allow the Runner to identify your module, render its settings UI, and control its lifecycle.
How does the PowerToys Runner load custom modules?
The Runner loads modules by searching for the powertoy_create symbol in DLLs listed in its internal registry within src/runner/main.cpp. It dynamically loads each DLL using LoadLibrary, retrieves the factory function via GetProcAddress, and calls it to obtain a PowertoyModuleIface instance. The Runner then manages this instance throughout the application lifecycle.
Where are module settings stored on disk?
Settings are persisted as JSON files in the user's local application data folder (%LOCALAPPDATA%\PowerToys\). The PowerToyValues::save_to_settings_file() method writes to a file named {module_key}-settings.json, where {module_key} is the string returned by your get_key() implementation.
Can a PowerToys module implement multiple hotkeys?
Yes. Return a count greater than one from get_hotkeys() and populate the buffer with multiple Hotkey structures, each with a unique id field. The on_hotkey() method receives the id of the triggered hotkey, allowing you to distinguish between different shortcuts within the same module.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →