Fluxta

Operand options

Fill a dropdown with real values while the user edits.

When a user builds a condition like "current scene equals ___", the default is a text box. They have to know the scene is called Starting Soon, spell it correctly, and update it by hand if it is renamed.

An Options Source replaces that with a dropdown the plugin fills in, listing the scenes that actually exist right now.

This happens at edit time, not when the Action runs. Fluxta asks the plugin while the user has the editor open.

Two steps

Declare the options key

Add an operand to the source whose comparison value should become a dropdown:

manifest.json
{
  "sources": [
    {
      "key": "currentScene",
      "name": "Current scene",
      "type": "string",
      "operand": { "editor": "select", "options": "scenes" }
    }
  ]
}

"scenes" is a key you invent. It is the link between this declaration and the code below, and nothing else.

Register the resolver

apps/plugin/src/index.ts
plugin.registerOptions({
  key: "scenes",
  resolve: async () => {
    const scenes = await obs.listScenes();
    return scenes.map((name) => ({ value: name, label: name }));
  },
});

await plugin.connect(); // register first, then connect

value is what gets stored in the condition; label is what the user reads. They are often the same, but not always — store a stable id and show a friendly name where possible:

return inputs.map((input) => ({ value: input.id, label: input.displayName }));

Where an options key is used

The same mechanism appears in three spots, and one Options Source can back all of them. resolve is not told which one asked.

A parameter of a source, via params[].editor and params[].options:

{
  "key": "sceneActive",
  "name": "Scene is active",
  "type": "boolean",
  "resolve": "query",
  "params": [
    { "key": "scene", "name": "Scene", "editor": "select", "options": "scenes" }
  ]
}

The value a condition compares against, via the source's operand:

{
  "key": "currentScene",
  "name": "Current scene",
  "type": "string",
  "operand": { "editor": "select", "options": "scenes" }
}

The value an Event filter compares against, via an event field's operand:

{
  "key": "channel",
  "name": "Channel",
  "type": "string",
  "operand": { "editor": "select", "options": "channels" }
}

Both keys in operand are required, and editor must be exactly "select". Anything else falls back to a plain input, silently — a typo here surfaces as "my dropdown is a text box".

What resolve is told

resolve: (ctx) => {
  ctx.args; // the surrounding source's params, if it has any
};

ctx.args carries the source's parameters as the user has filled them in so far, which lets one list narrow another: the scene picked in a parameter can decide which inputs to offer for the operand.

An Event field's operand has no parameters, so args is {}.

There is deliberately no itemId

The list is global. The user may be configuring a button that has not been saved yet, so there is no button to scope to — and a dropdown of scenes does not depend on which button will switch to one.

Failure is quiet, and safe

If the plugin is offline or slow, or resolve throws or returns nothing, the list is empty. No error is shown.

One thing never happens: a saved value is never lost. Whatever the condition already holds stays selected and stays stored, even when the plugin cannot produce a list containing it. A user editing a button while OBS is closed does not come back to a blanked-out condition.

Because an empty dropdown looks identical to "there is nothing to pick", return something meaningful where possible, and keep the list cheap — it is fetched while someone is waiting on a UI.

When not to use this

Options Sources fill in conditions and Event filters only, the places where Fluxta renders the editor itself.

For a dropdown in a plugin's own Action Editor form, Fluxta is not drawing the UI, so there is nothing to hook into. Fetch the list over messaging and render your own Select.

On this page