Skip to main content

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:

  • ModuleBase is the recommended base class. It seals the ABI entry points (Install, Uninstall, BindHotkeys, …) and gives you named hooks like OnInstall to override. See Plugin Lifecycle.
  • Log::Info formats the message in your plugin with std::format, then hands the finished string to the host across a POD sink. No logging library is linked into your DLL.
  • FRAMELIFT_MODULE_ENTRY generates the Qt plugin factory the host loads to create, destroy, and ABI-check your module. A plugin that draws UI is a QObject view-model and sets .renderOrder instead 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 .cpp files (such as the Log forwarder) 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 ModuleBase gives you and when it runs.
  • Settings — persist configuration and add a settings page.
  • Rendering UI — draw your own panel with embedded QML.