Editor API
Everything an editor page can do.
Editors are web pages that Fluxta mounts in a sandboxed iframe. There are two, and they share most of their surface:
ActionEditor— the settings form for one placed Action.PluginEditor— the settings page for the plugin as a whole.
import { ActionEditor, PluginEditor } from "@fluxta/sdk/api";Action Editor
const editor = new ActionEditor();
await editor.connect();ActionEditor
new ActionEditor()
Throws unless it is running inside the Fluxta editor iframe. Specifically, it throws when:
- there is no browser
window, - the URL fragment carries no editor grant, or
- the query string carries no
idfor the Action being edited.
Fluxta supplies both; neither is constructed by the plugin. The practical consequence is that an editor cannot run standalone against a dev server — build it and let Fluxta mount it.
connect
connect(): Promise<void>
Opens the connection and resolves once it is established. Reconnects after ordinary network failures; rejects on authorization, policy, and version failures, which are final.
getActionSettings
getActionSettings(): Promise<JsonValue>
The settings currently saved for this placement. Use it to fill in the form when the editor mounts.
const settings = (await editor.getActionSettings()) as Settings;Returns {} when nothing has been saved yet, when the stored value is null,
and when it cannot be parsed. A parse error never needs handling, but the result
should be treated as untrusted and its shape validated.
onActionSave
onActionSave(handler: () => unknown): () => void
Registers the function Fluxta calls when the user saves. Whatever it returns is serialized to JSON and stored as this placement's settings. Returns a function that unregisters the handler.
const off = editor.onActionSave(() => ({ scene: selectedScene }));Only one handler is live at a time
Registering a second handler replaces the first. With React state, re-register on every change — or read from a ref — or the handler keeps saving the value it closed over when it was first registered.
useEffect(() => editor.onActionSave(() => ({ scene })), [scene]);If the handler throws, or returns something that cannot be serialized — that includes returning nothing at all — the error is logged and the save stores nothing rather than a half-serialized value. The previously stored settings are kept, and Fluxta reports the failed save in the editor dialog instead of closing it.
Run Variables
The Run Variables readable at this Action's position — what an author could reference from this Action's settings right now. Fluxta works the set out so a form can offer a picker instead of asking people to remember an address.
getRunVariables
getRunVariables(): RunVariableDescriptor[]
The current list. Empty until Fluxta's first push arrives, shortly after the
editor mounts. The same array comes back until the next push, so it works as a
useSyncExternalStore snapshot.
onRunVariablesChange
onRunVariablesChange(listener): () => void
Subscribes to updates. Fluxta pushes a new list whenever the surrounding context changes — steps reordered, an Event's subscriptions edited — so an open editor stays current. Returns an unsubscribe function.
The listener is not called with the current list, only with pushes that arrive
after it subscribed. Read getRunVariables() once subscribed, not only before —
Fluxta sends the first list just once, and a push between the two is otherwise
missed. In React, useSyncExternalStore does both in the right order:
const subscribe = (onChange: () => void) =>
editor.onRunVariablesChange(onChange);
const getSnapshot = () => editor.getRunVariables();
export function useRunVariables() {
return useSyncExternalStore(subscribe, getSnapshot);
}RunVariableDescriptor
type RunVariableDescriptor = {
address: string; // "<plugin>.<action-or-event>.<key>"
label: string; // human-readable name
type: "string" | "number" | "boolean";
};referenceTo
referenceTo(variable): string
Turns a descriptor into the text to insert into a settings field — the address
wrapped in {{ }}.
import { referenceTo } from "@fluxta/sdk/api";
referenceTo(variable); // "{{obs.switch-scene.previousScene}}"Nothing sensitive travels in the list
The list carries an address, a label, and a type. No credentials, and nothing from another plugin's settings.
TemplateField is a ready-made
input that does all of this, for editors that do not need a custom picker.
Plugin Editor
const editor = new PluginEditor();
await editor.connect();PluginEditor is BaseEditor with nothing added. It has no Action settings, no
Run Variables, and no id — it is not editing any particular placement.
PluginEditor
new PluginEditor()
Throws unless it is running inside the Fluxta settings iframe: it needs a browser
window and an editor grant in the URL fragment.
Which plugin it belongs to comes from that grant, not from anything passed in, so an editor cannot reach another plugin's settings.
Mount it by declaring a top-level editor in the manifest, and use
editorPlacement to choose between the settings page and its own navigation
item.
Shared surface
Everything below is on both editors.
getPluginSettings
getPluginSettings(): Promise<{ event: string; payload: JsonValue }>
Fetches the plugin-wide settings — the same store the background process reads.
The value is on payload.
const settings = await editor.getPluginSettings();
const config = settings.payload;These are answered by Fluxta itself, so they work even when the background process is not running.
setPluginSettings
setPluginSettings(data: JsonValue): Promise<void>
Replaces the stored plugin settings wholesale.
await editor.setPluginSettings({ host, port });sendToPlugin
sendToPlugin<T extends JsonValue>(data: T): Promise<void>
Sends a message to the background process, received there via
onReceiveFromEditor.
await editor.sendToPlugin({ event: "testConnection" });If the background process is not running, the message goes nowhere — no error and no queue. Design the editor to work with the plugin offline, and to ask again once it hears from it.
onReceiveFromPlugin
onReceiveFromPlugin<T>(listener): () => void
Receives what the background process sent with sendToEditor. Returns an
unsubscribe function.
const off = editor.onReceiveFromPlugin<Status>((data) => setStatus(data));onTerminal
onTerminal(listener: (error: Error) => void): () => void
Observes a final disconnect after a successful connect. Nothing is retried afterwards; show the user that the editor needs reopening.
A typical Action Editor
import { ActionEditor } from "@fluxta/sdk/api";
import { useEffect, useState } from "react";
const editor = new ActionEditor();
await editor.connect();
export function App() {
const [scene, setScene] = useState("");
const [scenes, setScenes] = useState<string[]>([]);
// 1. Fill the form from what was saved.
useEffect(() => {
editor.getActionSettings().then((saved) => {
setScene((saved as { scene?: string }).scene ?? "");
});
}, []);
// 2. Tell Fluxta what to store. Re-registered whenever `scene` changes.
useEffect(() => editor.onActionSave(() => ({ scene })), [scene]);
// 3. Take live data from the background process...
useEffect(
() =>
editor.onReceiveFromPlugin<{ event: string; scenes: string[] }>(
(data) => {
if (data.event === "scenes") setScenes(data.scenes);
},
),
[],
);
// 4. ...and ask for it on mount.
useEffect(() => {
void editor.sendToPlugin({ event: "getScenes" });
}, []);
return <SceneSelect value={scene} options={scenes} onChange={setScene} />;
}