Skip to main content
Every FDO plugin follows a deterministic lifecycle. The FDO host drives each phase by sending specific messages to your plugin process. Understanding this sequence helps you write plugins that initialize correctly, render predictably, and handle errors gracefully.

Lifecycle overview

new MyPlugin()         ← constructor / registration

   init()              ← host sends PLUGIN_INIT

   render()            ← host sends PLUGIN_RENDER

renderOnLoad()         ← serialized alongside render(), runs in iframe on mount

UI message handlers    ← host sends UI_MESSAGE for each user interaction
1

Constructor

When you instantiate your plugin class (new MyPlugin()), the base FDO_SDK constructor runs automatically. It registers the plugin instance with PluginRegistry and signals readiness to the host via the communicator.
import { FDO_SDK, FDOInterface, PluginMetadata } from "@anikitenko/fdo-sdk";

export default class MyPlugin extends FDO_SDK implements FDOInterface {
    private readonly _metadata: PluginMetadata = {
        name: "My Plugin",
        version: "1.0.0",
        author: "Your Name",
        description: "Plugin description",
        icon: "cog",
    };

    get metadata(): PluginMetadata {
        return this._metadata;
    }

    init(): void { /* ... */ }
    render(): string { /* ... */ }
}

new MyPlugin();
The new MyPlugin() call at the bottom of the file is required. The FDO host expects the plugin to self-register at module load time.
2

init()

The host sends a PLUGIN_INIT message to trigger your init() method. Use this phase to set up handlers, configure storage, and perform any one-time initialization work.
init(): void {
    this.info("Plugin initializing", { plugin: this.metadata.name });

    // Register a UI message handler
    PluginRegistry.registerHandler("fetchData", async (input) => {
        const store = PluginRegistry.useStore("default");
        return store.get("data");
    });

    // Log the plugin's log directory
    this.debug("Log directory", { path: this.getLogDirectory() });
}
The PLUGIN_INIT payload may include:
  • content.apiVersion — the host’s expected API version. The SDK validates major-version compatibility against FDO_SDK.API_VERSION and throws if they differ.
  • content.capabilities — the set of capabilities the host grants to this plugin instance.
What happens when init() fails: If init() throws, PluginRegistry.callInit() re-throws the error. The host receives a response with an empty capabilities list and an error field. Your plugin will not proceed to the render phase.
3

render()

After a successful init(), the host sends a PLUGIN_RENDER message. Your render() method must return a UI string synchronously. The SDK serializes this string via serializeRender() and sends it back to the host, which then mounts it inside a sandboxed iframe.
render(): string {
    return `
        <div class="plugin-root">
            <h1>Hello from My Plugin</h1>
            <button onclick="window.createBackendReq('fetchData', {})">
                Load Data
            </button>
        </div>
    `;
}
render() must be synchronous. If it returns a Promise, the SDK throws immediately:
Error: Method 'render' must return a synchronous string. Async render promises are not supported.
Move async work into init() or into registered UI message handlers instead.
What happens when render() fails: If render() or serializeRender() throws, PluginRegistry.callRenderer() records the error and re-throws. The host receives a fallback error UI and an error field in the render response.
4

renderOnLoad() (optional)

renderOnLoad() returns an optional string that the host executes inside the iframe after it mounts your render output. Use it to set up DOM event listeners, initialize third-party UI libraries, or attach iframe-side logic that must run after the DOM is ready.
renderOnLoad(): string {
    return `() => {
        window.waitForElement("#data-table", (el) => {
            // Initialize ACE editor or other DOM-dependent setup
            el.style.opacity = "1";
        });
    }`;
}
The default implementation (inherited from FDO_SDK) returns '() => {}' — an empty function string — so you only need to override this method when you have on-load work to do.
Like render(), renderOnLoad() must be synchronous. The SDK uses serializeRenderOnLoad() to package the string for host transport. Returning a Promise throws the same async error as render().
5

UI message handlers

After mounting, your plugin UI communicates back to the plugin backend by calling window.createBackendReq(handlerName, payload). The host routes these calls as UI_MESSAGE messages to your registered handlers.Register handlers in init() using PluginRegistry.registerHandler:
init(): void {
    PluginRegistry.registerHandler("getStatus", async (_input) => {
        const store = PluginRegistry.useStore("default");
        return { status: store.get("status") ?? "idle" };
    });

    PluginRegistry.registerHandler("updateStatus", async (input: { status: string }) => {
        const store = PluginRegistry.useStore("default");
        store.set("status", input.status);
        return { ok: true };
    });
}
Then call handlers from your UI:
render(): string {
    return `
        <button onclick="window.createBackendReq('getStatus', {}).then(r => console.log(r))">
            Check Status
        </button>
    `;
}
What happens when a handler fails: If a registered handler throws, PluginRegistry.callHandler() catches the error, logs it, and returns null. The host receives { error: string } in the UI_MESSAGE response.If the handler name is not registered, callHandler logs a warning and returns null.

Message types reference

MessageWhen sentSDK action
PLUGIN_INITHost loads the pluginCalls PluginRegistry.callInit() → your init()
PLUGIN_RENDERAfter successful initCalls PluginRegistry.callRenderer() → your render() + renderOnLoad()
UI_MESSAGEUser interacts with the plugin UICalls PluginRegistry.callHandler(name, data) → your handler

Error behavior summary

PhaseError behavior
init() throwsHost receives empty capabilities + error. Plugin does not render.
render() throwsHost receives fallback error UI + error in render response.
render() returns a PromiseSDK throws immediately with a clear message before serialization.
renderOnLoad() returns a PromiseSDK throws immediately with a clear message before serialization.
Handler throwsHandler returns null; host receives { error: string }.
Handler not foundHandler returns null; warning is logged.

Full minimal example

import { FDO_SDK, FDOInterface, PluginMetadata, PluginRegistry } from "@anikitenko/fdo-sdk";

export default class MinimalPlugin extends FDO_SDK implements FDOInterface {
    private readonly _metadata: PluginMetadata = {
        name: "Minimal Plugin",
        version: "1.0.0",
        author: "Your Name",
        description: "Demonstrates the full plugin lifecycle",
        icon: "info-sign",
    };

    get metadata(): PluginMetadata {
        return this._metadata;
    }

    init(): void {
        this.info("Initializing");

        PluginRegistry.registerHandler("ping", async () => ({ pong: true }));
    }

    render(): string {
        return `
            <div>
                <p>Plugin is running.</p>
                <button onclick="window.createBackendReq('ping', {}).then(r => alert(JSON.stringify(r)))">
                    Ping backend
                </button>
            </div>
        `;
    }

    renderOnLoad(): string {
        return `() => {
            console.log("Plugin UI mounted.");
        }`;
    }
}

new MinimalPlugin();