Fluxta

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

manifest.json
{
  "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. TemplateField from 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.

On this page