Skip to content

Writing a native plugin

A minimal native plugin is one class, one factory macro, one JSON file, and a short CMakeLists.txt. You can author it entirely out-of-tree against the Plugin SDK — no MTE source checkout required.

Native or Python?

This page covers native C++ plugins (compiled to a .dll/.so/.dylib and loaded by the Qt loader). If you'd rather ship a folder of .py files the editor spawns as a subprocess, see Python plugins — same PluginApi surface, different backend, no compiler required.

Draft

This walkthrough is grounded in PLUGIN_ARCHITECTURE.md; verify the exact SDK package name and mte_add_plugin() arguments against the shipped SDK before relying on them.

1. Implement IPlugin

#include <PluginApi/IPlugin.h>
#include <PluginApi/IPluginContext.h>

class HelloWorldPlugin : public MTE::plugin::IPlugin {
public:
    MTE::plugin::PluginInfo info() const override {
        return { "org.mte.helloworld", "Hello World", "1.0.0",
                 "Example Vendor", "Says hi.", MTE_PLUGIN_API_VERSION };
    }

    bool initialize(MTE::plugin::IPluginContext& ctx) override {
        m_cmd = ctx.commands().registerCommand(
            { "helloworld.sayHi", "Say Hi", "Ctrl+Alt+H", "Hello" },
            [&ctx] { ctx.log().log(MTE::plugin::LogLevel::Info, "Hi!"); });
        m_menu = ctx.menus().addItem("Plugins/Say Hi", "helloworld.sayHi");
        return true;
    }

    void shutdown() noexcept override {}   // tokens tear down automatically

private:
    MTE::plugin::CommandToken m_cmd;
    MTE::plugin::MenuToken    m_menu;
};

2. Export the factory (native Qt backend)

#include <PluginApi/Qt/PluginQtGlue.h>

class HelloWorldFactory : public QObject, public MTE::plugin::IPluginFactory {
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.mte.IPluginFactory/1" FILE "plugin.json")
    Q_INTERFACES(MTE::plugin::IPluginFactory)
public:
    std::unique_ptr<MTE::plugin::IPlugin> create() override {
        return std::make_unique<HelloWorldPlugin>();
    }
};

3. Describe it in plugin.json

{
  "id":         "org.mte.helloworld",
  "name":       "Hello World",
  "version":    "1.0.0",
  "apiVersion": 1,
  "entry":      "native",
  "requires":   [],
  "order":      600,
  "menus": [
    { "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
  ]
}

The apiVersion here and the one returned by info() must both equal MTE_PLUGIN_API_VERSION, or the host refuses to load the plugin.

Attributing bundled libraries — thirdParty

If your plugin bundles or links a third-party component, declare it in an optional thirdParty array so the editor can honour its attribution obligations:

"thirdParty": [
  { "name": "Qt WebEngine", "license": "LGPL-3.0",
    "url": "https://doc.qt.io/qt-6/qtwebengine-index.html",
    "file": "QtWebEngine-LGPLv3.txt" }
]

Each entry needs a name (entries without one are dropped); license, url, and file are optional. Your plugin owns the licence texts — ship them next to the plugin under Plugins/Licenses/<your-plugin-id>/, where file is the basename. The About dialog lists every installed plugin's thirdParty components and points users at that folder; the core never carries a plugin's third-party texts for it. (The bundled HtmlPreviewPlugin, which embeds QtWebEngine + Chromium, is the reference example.)

4. Build it

Use the SDK's CMake helper:

find_package(MTEPluginSDK REQUIRED)
mte_add_plugin(NAME HelloWorldPlugin
    SOURCES Src/HelloWorldPlugin.cpp
    # TRANSLATIONS Translations/HelloWorld_en.ts ...
)

5. Install & run

Either zip the built library + plugin.json as <name>.mteplugin and install it via Preferences ▸ Plugins ▸ Install… (the package is validated — manifest, archive safety, and the binary's embedded metadata — before anything is touched), or drop the files into a discovered plugin directory by hand (see Architecture overview). Restart the editor; enable it in Preferences ▸ Plugins if needed.

Next