Plugin settings
One set of settings for the whole plugin.
Some configuration does not belong to a single button. An API token, a host and port, an account to connect to — those are properties of the plugin, and repeating them on every placement would be wasteful.
Plugin settings are exactly that: one JSON value per plugin, readable and writable from both the background process and any editor.
Reading them
In the background process, subscribe rather than fetch. onPluginSettings fires
immediately with the current value and on every later change:
type Config = { host?: string; port?: number };
await plugin.onPluginSettings<Config>((config) => {
obs.reconnect(config.host ?? "127.0.0.1", config.port ?? 4455);
});The user changes the host in settings, and the plugin reconnects. No restart, no polling.
Use getPluginSettings() for a one-off read. Note the shape — the value is on
payload, not the response itself:
const settings = await plugin.getPluginSettings();
const config = settings.payload as Config;Writing them
setPluginSettings replaces the stored value. There is no merge, so read,
spread, write:
const current = (await plugin.getPluginSettings()).payload as Config;
await plugin.setPluginSettings({ ...current, port: 4456 });Forgetting the spread is how a plugin loses the user's token while updating an unrelated field.
The settings page
To give the user somewhere to edit them, declare a plugin-wide editor:
{
"editor": "editor/plugin.html",
"editorPlacement": "settings"
}editorPlacement chooses where it lives:
"settings"(the default) — a section on the app's settings page. Right for configuration the user sets once and forgets."navigation"— its own top-level item alongside Deck and Events. For a plugin whose page is something the user visits: a dashboard, a live log, a control panel.
Choose "settings" unless the page is a destination in its own right.
Building it
import { PluginEditor } from "@fluxta/sdk/api";
const editor = new PluginEditor();
await editor.connect();
export function App() {
const [host, setHost] = useState("");
const [status, setStatus] = useState("unknown");
// Load the stored settings.
useEffect(() => {
editor.getPluginSettings().then((settings) => {
setHost((settings.payload as Config).host ?? "");
});
}, []);
// Live status pushed by the background process.
useEffect(
() => editor.onReceiveFromPlugin<Status>((data) => setStatus(data.status)),
[],
);
return (
<>
<input value={host} onChange={(e) => setHost(e.target.value)} />
<button onClick={() => editor.setPluginSettings({ host })}>Save</button>
<button onClick={() => editor.sendToPlugin({ event: "testConnection" })}>
Test connection — {status}
</button>
</>
);
}PluginEditor is the Action Editor minus everything scoped to a placement: no
action settings, no Run Variables, no save handler. The page decides when to
save, because there is no surrounding dialog with a Save button.
Settings work even when the plugin does not
Plugin settings are answered by Fluxta itself, not by the background process. The settings page still loads and still saves while the plugin is offline — which matters, because a bad host is exactly the situation where the user needs to fix the setting and the plugin cannot connect.
Live data behaves the opposite way: sendToPlugin goes nowhere when the process
is down, with no error and no queue. Design the page to be useful in both states:
show the settings, and show that the connection is not up.
Which settings go where?
| Put it in plugin settings | Put it in action settings |
|---|---|
| API token, credentials | Which scene this button switches to |
| Host and port | The message this button sends |
| Account or workspace to connect to | The file this button plays |
| Anything the user sets once | Anything that differs per button |
When in doubt: if two placements would always hold the same value, it belongs in plugin settings.