Action settings
Build the form that configures one placement of your Action.
Most Actions need configuring: which scene, which file, what message. That configuration is per placement — the same Action on two buttons has two independent sets of settings.
The plugin provides the form. Fluxta stores what it returns and hands it back to
the Action as ctx.settings.
Declare the editor
{
"actions": [
{
"name": "Switch Scene",
"type": "switch-scene",
"editor": "editor/index.html"
}
]
}The path is relative to the plugin folder, and the file has to exist in the build
output. If it does not, the Action has no settings form and no error is raised.
An Action without an editor is valid; not everything needs configuring.
The two things an editor does
Read what was saved, and answer when Fluxta asks what to store. With the UI Kit that is one hook:
import { EditorPage, TextField, useActionSettings } from "@fluxta/sdk/ui";
type Settings = { scene?: string };
export default function App() {
const { values, set } = useActionSettings<Settings>();
return (
<EditorPage>
<TextField
label="Scene"
value={values.scene ?? ""}
onChange={set("scene")}
/>
</EditorPage>
);
}Fluxta decides when to save. There is no save function to call.
Talking to the background process is a third, optional capability, covered in messaging.
Underneath, and the trap it avoids
useActionSettings is built on two calls, and an editor that is not a React app
uses them directly:
import { ActionEditor } from "@fluxta/sdk/api";
const editor = new ActionEditor();
await editor.connect();
const saved = (await editor.getActionSettings()) as Settings;
editor.onActionSave(() => ({ scene: current }));onActionSave keeps exactly one handler, and it runs long after it was
registered. A handler that closed over a variable answers with whatever that
variable held at the time:
// Broken: always saves the initial "", whatever the user typed.
useEffect(() => {
editor.onActionSave(() => ({ scene }));
}, []);Re-register whenever the value changes — onActionSave returns an unregister
function, so returning it from the effect keeps this tidy — or read the current
value through a ref:
// Correct: re-registered on every change to `scene`.
useEffect(() => editor.onActionSave(() => ({ scene })), [scene]);This is the bug most people hit when wiring an editor by hand, and the reason
useActionSettings exists.
Reading the settings back
The Action receives whatever the handler returned, already parsed:
type Settings = { scene?: string };
class SwitchScene extends StandaloneAction<Settings> {
type = "switch-scene";
async onTrigger(ctx: StandaloneTriggerContext<Settings>) {
await obs.switchTo(ctx.settings.scene);
}
}The Settings type is a claim, not a guarantee. Stored settings are whatever was
last written, possibly by an older version of the plugin with a different shape.
Make every field optional, or validate before use.
getActionSettings() returns {} when nothing has been saved yet, so a fresh
placement is an empty object rather than an error.
Making the values good
Two things raise a settings form from workable to pleasant, both covered elsewhere:
- A dropdown of real values instead of a text box the user types a scene name into. See operand options for conditions, and messaging for fetching a live list into your own form.
- A picker for values from earlier steps, so a message field can include the
name of whoever triggered it.
TemplateFieldfrom the UI Kit provides this in one component.
Developing an editor
Two things regularly catch people out:
- An editor cannot run standalone.
new ActionEditor()needs a grant that only Fluxta supplies, so it throws in a normal browser tab. Build it and let the app mount it. - Rebuilding does not reload it. Editors are static files; nothing restarts when they change. Close the settings dialog and reopen it to pick up a new build.
If the editor opens blank, the cause is almost always absolute asset paths — see Troubleshooting.