Best practices
The things that go wrong quietly.
None of these are enforced. All of them go wrong quietly.
Handle concurrency
Steps inside one Run are sequential, but Runs are not. Two buttons pressed together, an Event firing while a button is held, several automations reacting to the same thing — each starts a Run, and they overlap in one process.
So this is a race:
// Broken: two concurrent Runs can both read `false`.
async onTrigger() {
if (this.busy) return;
this.busy = true;
await doWork();
this.busy = false;
}Anything Actions share — maps, counters, a connection, a reconnect flag — has to
tolerate two handlers being inside it at once. The usual fixes apply: key state
by ctx.itemId so two buttons cannot collide, or serialise through a promise
chain rather than a boolean.
Do not block a Run
Fluxta awaits onTrigger before running the next step. A handler that takes ten
seconds freezes that button's Action list for ten seconds.
- Put a timeout on every network call. A hung request holds the Run for as long as it hangs.
- Return early when there is nothing to do. Check the cheap conditions before the expensive work, not after it.
- If the work genuinely takes a while and the result is not needed by later steps, start it and return.
The same applies to a pull Value Source: its
resolve runs while a condition is waiting, and Fluxta gives it about one second
before treating the answer as absent. Read state already held; do not go to the
network from inside it.
Distrust stored settings
Settings persist across updates. A user upgrading from 1.0 to 2.0 hands the new code whatever 1.0 wrote, and nothing migrates it.
// The type is a claim, not a guarantee.
const port = Number(ctx.settings.port);
if (!Number.isFinite(port)) return;Make fields optional, provide defaults, and prefer adding a new optional field over repurposing an existing one.
Fail loudly enough to be found
A throwing onTrigger is caught and logged, and the Run continues. That is good
for the user's button and bad for noticing the failure.
console.errorwith enough context to identify which placement failed.- If a failure matters to the user's automation, expose it as a Value Source they can branch on rather than hoping they read a log.
- Do not swallow errors into an empty
catch. The log file is the only window into a running plugin.
Keep credentials safe
- Put tokens in plugin settings, never in an Action's settings, where they get copied onto every button that uses them.
- Never log a token, not even truncated, not even while debugging.
- Do not send credentials over the editor messaging channel. An editor can read plugin settings directly.
- Do not put anything sensitive into a URL.
Declare honestly
The manifest is what the user sees, and it is also what Fluxta enforces.
- Declare the narrowest
contextthat works."standalone"is usable in more places than"item", including automations with no button. - Declare real types. A count declared
"string"cannot be compared with "greater than", and users notice before the author does. - Do not declare Actions or Event Sources that are not implemented. They appear in the picker and then do nothing.
Push state on connect
A pushed Value Source is dropped when the plugin disconnects. Pushing only inside an event handler leaves everything absent after a reconnect until the next change happens, which might be hours.
await plugin.connect();
plugin.setVariable("connected", obs.isConnected);Be useful while offline
A background process will not always be running when an editor is open.
- Render editors from saved settings first, then enrich with live data.
- Show the offline state rather than an empty control. A dropdown with no options looks broken; "not connected" is an explanation.
- Never make a message the only way to get something the editor needs to render.
Bundle everything
Fluxta runs the entry file with its own Node runtime and does not install
node_modules next to it. An unbundled import fails at startup with a
module-not-found error and no plugin.
The generated project already inlines dependencies. If you change the build, keep it that way.
Keep paths relative
The editor is served from a nested path, so absolute /assets/... references
resolve to the wrong place and the page loads blank. With Vite, base: "./".
Respect what is not yours
An Action cannot change how its button looks, and that restriction is worth honouring in spirit as well as in code. A user should be able to look at their own Action list and understand everything the button does. Expose state as a Value Source and let them decide what to do about it, rather than reaching around the configuration they can see.