A real-world plugin: OBS
An official C# plugin, shown for reading
The OBS plugin is one of the official C# plugins: it is built and signed by the maintainer, and the server loads a C# plugin only with that signature. Plugins by other authors are JavaScript plugins. This page walks through it because it shows what a full integration looks like.
The OBS plugin (id obs) controls OBS Studio over obs-websocket v5, which is built into OBS 28 and newer. It is the largest plugin in the repository and uses almost everything the SDK offers, so it is the best example to read after the tutorials.
What it provides
- 22 actions in the OBS category: scenes, studio mode, transitions, streaming, recording, replay buffer, virtual camera, audio mute and volume, scene item visibility and text sources.
- About 45 live variables with the
obs.prefix, for exampleobs.streaming,obs.stream.duration,obs.scene.current,obs.stats.fps, plusobs.input.<slug>.mutedandobs.item.<scene>.<source>.visiblefor things that come and go. - A settings page (Enabled, Server, Port, Password) and a status bar item with the connection state.
It requires OBS 28 or newer with the WebSocket server turned on (Tools, WebSocket Server Settings in OBS). The README of the plugin lists every variable and action.
The entry point
using MacroGrid.Plugin.Abstractions;
namespace MacroGrid.Plugin.Obs;
/// <summary>Entry point (plugin.json's "entry") — the host finds this type by reflection and instantiates it
/// with a parameterless constructor, then calls <see cref="Initialize"/> once.</summary>
public sealed class ObsPlugin : IPlugin
{
public void Initialize(IPluginHost host)
{
var obs = new ObsConnection(host);
host.RegisterVariableProvider(obs);
host.RegisterSettingsPage(new ObsSettingsPage(host, obs));
host.RegisterAction(new ObsSetSceneAction(obs));
host.RegisterAction(new ObsSetPreviewSceneAction(obs));
host.RegisterAction(new ObsStudioTransitionAction(obs));
host.RegisterAction(new ObsToggleStudioModeAction(obs));
host.RegisterAction(new ObsSetTransitionAction(obs));
host.RegisterAction(new ObsSetProfileAction(obs));
host.RegisterAction(new ObsStartStreamAction(obs));
host.RegisterAction(new ObsStopStreamAction(obs));
host.RegisterAction(new ObsToggleStreamAction(obs));
host.RegisterAction(new ObsStartRecordAction(obs));
host.RegisterAction(new ObsStopRecordAction(obs));
host.RegisterAction(new ObsToggleRecordAction(obs));
host.RegisterAction(new ObsPauseRecordAction(obs));
host.RegisterAction(new ObsVirtualCamAction(obs));
host.RegisterAction(new ObsReplayBufferAction(obs));
host.RegisterAction(new ObsSaveReplayAction(obs));
host.RegisterAction(new ObsSetMuteAction(obs));
host.RegisterAction(new ObsToggleMuteAction(obs));
host.RegisterAction(new ObsSetVolumeAction(obs));
host.RegisterAction(new ObsAdjustVolumeAction(obs));
host.RegisterAction(new ObsSetItemVisibilityAction(obs));
host.RegisterAction(new ObsSetTextAction(obs));
}
}One object, ObsConnection, owns the connection. It is registered as the variable provider, handed to the settings page and to every action.
| File | Role |
|---|---|
ObsPlugin.cs | IPlugin: creates the connection and registers everything (above). |
ObsConnection.cs, ObsConnection.*.cs | IVariableProvider and IVariableCatalogSource. Runs for the plugin's lifetime: connects, keeps the variables up to date, updates the status item and reconnects with capped exponential backoff. One partial class, split by concern (loop, polling, events, variables, status). |
ObsClient.cs, ObsAuth.cs, ObsJson.cs | A small obs-websocket v5 client: handshake, authentication, requests and events. |
ObsState.cs | A cache of what OBS reported (scenes, inputs, scene items). |
Actions/*.cs | The actions, grouped by scenes, outputs, audio and sources. They share small base classes (ObsActionBase, ObsOptionsAction, ObsRequestAction, ObsModeAction) and implement IActionHandler, IActionDescriptor and, for dropdowns, IOptionsSource. |
ObsSettings.cs | ObsSettings (persisted as settings.json in DataDirectory) and ObsSettingsPage : IPluginSettingsPage. |
Patterns worth copying
- A long-running provider.
ObsConnection.RunAsyncruns until its token is cancelled. It creates its status item withhost.CreateStatusItem("connection"), publishes variables through theIVariableStoreit is given, and never lets a failed connection end the method: it retries with a capped exponential backoff, and a settings change wakes it up early. - Dynamic variables are removed. When an OBS input or scene item is deleted or renamed, the plugin removes its variable with
store.Remove(name), so it stops showing up in the picker. - Dropdowns from a cache. Actions implement
IOptionsSource; the scene, input and item lists come from the cache of what OBS reported, so they do not wait for a live round trip. - Clear failures. An action that targets something that no longer exists (a scene that was deleted in OBS) fails with a clear message. The server shows it on the phone and in the editor's status bar, so a stale button is never silent.
- Settings apply immediately. The settings page's
Savewritessettings.jsonand signals the connection, so a corrected host, port or password reconnects within moments. - Tests without OBS.
WebSocketBridgeForOBS/testsdrives the plugin against a fake obs-websocket server (FakeObsServer.cs). Rundotnet testonWebSocketBridgeForOBS/tests/MacroGrid.Plugin.Obs.Tests.
The plugin's user-visible strings (action names, form labels, status text) are written in English and translated to Turkish through WebSocketBridgeForOBS/locales/tr.json; the code, comments and docs are in English, which is the rule for every plugin in the repository.
Using it in a layout
- Install the plugin (Plugin basics), open its settings from the gear button and turn Enabled on (the plugin starts disabled).
- Add a button with the text
{obs.scene.current}and bind the set-scene action to it. - Add a label
Live {obs.stream.duration}and bind the stream actions to a toggle.
