Your First Plugin
This walks through the smallest plugin that does something observable: it logs a
line when FrameLift installs it. It is the same shape as the hello-plugin
example in the FrameLift-Examples
repository.
A plugin is one Qt plugin DLL/SO with one module (its IModule). The plugin is
described by a single .Plugin.json; the CMake helper compiles that metadata into
the binary, so your source only declares the module class and its entry point.
1. The module source
MyPlugin.h:
#pragma once
#include <framelift/core.h>
class MyPlugin : public ModuleBase
{
protected:
// Required: used as the INI section, settings page title, and log label.
const char* ModuleName() const override { return "MyPlugin"; }
// One-time setup. Called after the host services are registered.
void OnInstall(IModuleContext& ctx) override;
};
// Export the plugin's entry point. This module draws no UI, so it opts out with
// .qml = false (and need not be a QObject). Plugin identity (name, version,
// publisher, ABI) comes from the JSON metadata, not this macro.
FRAMELIFT_MODULE_ENTRY(MyPlugin, {
.qml = false,
})
MyPlugin.cpp:
#include "MyPlugin.h"
void MyPlugin::OnInstall(IModuleContext& /*ctx*/)
{
Log::Info("[MyPlugin] hello from the FrameLift SDK!");
}
Three things are happening:
ModuleBaseis the recommended base class. It seals the ABI entry points (Install,Uninstall,BindHotkeys, …) and gives you named hooks likeOnInstallto override. See Plugin Lifecycle.Log::Infoformats the message in your plugin withstd::format, then hands the finished string to the host across a POD sink. No logging library is linked into your DLL.FRAMELIFT_MODULE_ENTRYgenerates the Qt plugin factory the host loads to create, destroy, and ABI-check your module. A plugin that draws UI is aQObjectview-model and sets.renderOrderinstead of.qml = false. See Export Macros and Rendering UI.
2. The plugin metadata
MyPlugin.Plugin.json — the single plugin descriptor:
{
"fileVersion": 1,
"id": "example.my_plugin",
"name": "MyPlugin",
"publisher": "Acme",
"description": "Does a thing",
"version": "1.0.0",
"abi": 1,
"enabled": true,
"provides": { "features": ["example.my_plugin"] },
"requires": { "plugins": [], "features": [] },
"optional": { "plugins": [], "features": [] },
"platforms": []
}
3. The build file
CMakeLists.txt:
cmake_minimum_required(VERSION 3.28)
project(MyPlugin LANGUAGES CXX)
find_package(FrameLiftSdk REQUIRED PATHS "/path/to/framelift-sdk/cmake" NO_DEFAULT_PATH)
add_framelift_plugin(MyPlugin
PLUGIN_JSON "${CMAKE_CURRENT_SOURCE_DIR}/MyPlugin.Plugin.json"
MyPlugin.cpp
${FRAMELIFT_SDK_SOURCES})
find_package(FrameLiftSdk)locates the SDK. It is gated on the exact ABI version (ExactVersion), so an incompatible SDK fails here at configure time rather than at load time.add_framelift_plugin(... PLUGIN_JSON ...)declares the plugin DLL/SO target, validates the JSON metadata, and compiles it into the binary.${FRAMELIFT_SDK_SOURCES}are the SDK helper.cppfiles (such as theLogforwarder) that compile directly into your plugin.
4. Build
cmake -B build
cmake --build build
# → build/plugins/acme.my_plugin.so
The artifact name is the lowercase plugin id (publisher.plugin).
5. Load it
Copy the plugin DLL/SO into plugins/ next to the framelift executable, then
launch FrameLift. Plugins are enabled by default, so there is nothing else to
configure. Check the log — you should see:
[MyPlugin] hello from the FrameLift SDK!
To stop the plugin loading later, set example.my_plugin=disabled in
plugins.ini (or toggle it from Settings → Plugins).
Next
A plugin that only logs is not very useful. From here:
- Plugin Lifecycle — every hook
ModuleBasegives you and when it runs. - Settings — persist configuration and add a settings page.
- Rendering UI — draw your own panel with embedded QML.