Fluxta

Events

Announce something happened and start an automation.

Everything so far starts with a button. Events are the other direction: something happens out in the world — a chat message, a follow, a scene change — and the user has an automation waiting for it.

A plugin's job is to announce those happenings. What runs in response is the user's business: they create an Event, pick the Event Source as what fires it, optionally filter it, and choose the Actions to run.

Declare what you can announce

manifest.json
{
  "events": [
    {
      "type": "chat-message",
      "name": "Chat message",
      "fields": [
        { "key": "user", "name": "Username", "type": "string" },
        { "key": "text", "name": "Message", "type": "string" },
        { "key": "isMod", "name": "From a moderator", "type": "boolean" }
      ]
    }
  ]
}

name is what the user picks from a list. type is the identifier passed to emitEvent.

Fields are the important part. They are what the user can filter on and what later steps can read. An Event with no fields can only ever mean "this happened", which is rarely enough — a chat message whose text nobody can read cannot do much.

Announce it

apps/plugin/src/index.ts
chat.on("message", (message) => {
  plugin.emitEvent("chat-message", {
    user: message.author,
    text: message.text,
    isMod: message.badges.includes("moderator"),
  });
});

That is the whole API. It is fire-and-forget: nothing is awaited, and the plugin learns nothing about what ran, or whether anything did.

Two things are dropped silently:

  • an event type not declared in events[]
  • a payload key not declared in that Event Source's fields[]

A field declared but absent from the payload is simply missing for that firing.

Types are checked against the declaration too, so isMod must be a real boolean, not "true".

What users do with the fields

Filter

The user can narrow an Event: only messages from a moderator, only from a specific channel. That is why field types matter — a boolean field gives a clean toggle, a number gives "greater than".

A filter's value can be a dropdown of live choices, with operand options on the field:

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

Read them in the Actions

Inside that Event's Action list, every field is readable as a Run Variable, addressed <plugin>.<event-type>.<field-key>:

{{twitch.chat-message.user}}

A user can therefore build "when a chat message arrives, say hi {{twitch.chat-message.user}}" with no further work on the plugin's side. Declaring good fields is what makes that possible, and it is where most Run Variable references come from.

Running without a button

An Event's Action list may have no button attached at all, which is the practical argument for preferring StandaloneAction: an ItemAction is only usable in automations bound to a button, so declaring "context": "item" without needing it narrows where the Action can be used.

Rate and volume

Nothing throttles emitEvent. A chat plugin emitting one Event per message in a busy channel starts one Run per message, and unqueued Events run in parallel.

Managing that is the user's configuration, but a plugin can help:

  • Do not emit what nobody can use. Every Event Source declared is one more thing in their list; every firing is potential work.
  • Emit at a meaningful granularity. One scene-changed per change, not one per property of the new scene.
  • Do not re-emit history on reconnect. A user does not want the last hour of chat replayed because a socket blipped.

On this page