Fluxta

Actions

Declare an Action and run code when it fires.

An Action is a step the user drops into a button's Action list or into an automation. When the step is reached, the plugin's code runs.

Every Action has two halves that must agree: a declaration in manifest.json and a class in the background process.

Declare it, then implement it

manifest.json
{
  "actions": [
    {
      "name": "Switch Scene",
      "type": "switch-scene",
      "context": "standalone"
    }
  ]
}
apps/plugin/src/index.ts
import { StandaloneAction, StandaloneTriggerContext } from "@fluxta/sdk/api";

type Settings = { scene?: string };

class SwitchScene extends StandaloneAction<Settings> {
  type = "switch-scene"; // === the manifest's `type`

  async onTrigger(ctx: StandaloneTriggerContext<Settings>) {
    await obs.switchTo(ctx.settings.scene);
  }
}

plugin.registerAction(new SwitchScene());
Manifest fieldPurpose
nameThe label the user picks from a list.
typeThe identifier. The class's type property must match it.

A `type` mismatch fails silently

If the manifest's type and the class's type drift apart, Fluxta routes nothing to the Action and reports no error. This is the most common reason a new Action does nothing.

Choosing a base class

The question is whether the Action needs to know which button it is on.

NeedBase classManifest
No — play a sound, send a messageStandaloneAction"context": "standalone", or omit it
Yes — start a timer for this buttonItemAction"context": "item"

StandaloneAction works everywhere, including in automations that have no button at all. ItemAction receives ctx.itemId, which is the key to use for per-button state.

Prefer StandaloneAction when either would work, because an automation triggered by a chat message has no button to pass along.

`context` defaults to standalone

Omitting context produces a standalone Action. A class that extends ItemAction without the matching manifest field receives a context with no itemId. Declare it explicitly whenever the button is needed.

What onTrigger receives

The context carries the placement, the button, and the saved settings:

async onTrigger(ctx: ItemTriggerContext<Settings>) {
  ctx.uuid;      // this placement's stable id
  ctx.itemId;    // the button (ItemAction only)
  ctx.settings;  // what the user configured, already parsed
}

ctx.settings holds the value the Action Editor saved, with any Run Variable references already resolved. Nothing needs to be parsed and nothing needs to be opted into.

Returning values

Returning an object hands values to later steps of the same Run:

async onTrigger(ctx: StandaloneTriggerContext<Settings>) {
  const previous = await obs.currentScene();
  await obs.switchTo(ctx.settings.scene);
  return { previousScene: previous };
}

Each key must be declared in the manifest's produces[] first — see Run Variables. Return nothing when there is nothing to hand on.

Failing

If onTrigger throws, the error is logged, the step produces no values, and the rest of the Action list still runs. One failing step does not abandon the remainder of a button's configuration.

A failure is therefore invisible unless the plugin makes it visible. To surface a problem, log it or expose it as a Value Source the user can branch on.

Async work

onTrigger may return a promise, and Fluxta awaits it before running the next step. This is what makes "switch the scene, then read what it was" work.

Waiting blocks the Run. Keep handlers as short as the work allows, and avoid awaiting anything that may never resolve — a network call with no timeout holds the user's button for as long as it hangs.

Two Runs can overlap

Steps inside one Run are sequential, but separate Runs are not. Two buttons pressed together, or two automations firing together, put two onTrigger calls in flight at once, in the same process, sharing the same variables. See Best practices.

What an Action cannot do

An Action cannot change how its button looks — not the title, icon, colour, or state. Those are separate steps the user places and can see in the Action list.

The restriction is deliberate: a button that changes for reasons absent from the user's own configuration cannot be debugged by that user. To express a state, expose it as a Value Source and let the user decide what the button does about it.

On this page