Cross-platform .NET 8 SDK for Elgato Stream Deck WebSocket plugins.
This repository is not a USB HID driver. Stream Deck Module / direct hardware protocols are out of scope. The SDK talks to the Stream Deck application over the official plugin WebSocket.
streamdeck-tools 7.0 already covers Windows + macOS drawing via SkiaSharp. StreamDeckPluginSharp focuses on the remaining gaps:
- Full Stream Deck plugin protocol v2.0 coverage (
didReceiveDeepLink,deviceDidChange, secrets, resources,setTriggerDescription, Stream Deck + dials, and more) - Async-first handlers that return
Taskand acceptCancellationToken - System.Text.Json, no Newtonsoft / NLog / CommandLineParser dependency
- Shared stateful services through
Microsoft.Extensions.DependencyInjection - Property Inspector bindings so React forms stay in sync with C# settings without handwritten WebSocket glue
- Windows + macOS packaging from the first sample (
CodePath/CodePathMac, self-contained publish)
await StreamDeckPlugin.RunAsync(args);
[StreamDeckAction("dev.example.plugin.counter")]
public sealed class CounterAction : KeyActionBase<CounterSettings>
{
public override async Task OnKeyDownAsync(ActionPayload payload, CancellationToken cancellationToken)
{
await UpdateSettingsAsync(settings => settings.Count++, cancellationToken);
await SetTitleAsync(Settings.Count.ToString(), cancellationToken: cancellationToken);
}
}Share an external connection across every key:
var builder = StreamDeckPlugin.CreateBuilder(args);
builder.Services.AddSingleton<ObsConnectionService>();
await using var plugin = builder.Build();
await plugin.RunAsync();public sealed class MuteAction(ObsConnectionService obs) : KeyActionBase<MuteSettings>
{
public override Task OnKeyDownAsync(ActionPayload payload, CancellationToken cancellationToken)
=> obs.ToggleMuteAsync(cancellationToken);
}Implement IPluginService on that singleton to start and stop the remote socket with the plugin process. Inject IStreamDeckConnection when background code needs to update titles or images.
Mark contracts with [TypeScriptContract], generate TypeScript, and bind inputs:
const { settings, bind } = useSettings<CounterSettings>();
return <input type="number" {...bind("increment")} />;StreamDeckProvider owns connectElgatoStreamDeckSocket. See docs/property-inspector.md.
samples/CounterSample shows:
- a keypad action and a Stream Deck + dial sharing one
CounterStore - typed settings synchronized with a React inspector
publish.ps1/publish.shforwin-x64,osx-arm64, andosx-x64./pack.ps1(or./pack.sh) for NuGet packages and a.streamDeckPlugininstaller
./samples/CounterSample/publish.ps1 -Install -Pack| Package | Purpose |
|---|---|
StreamDeckPluginSharp |
Plugin host, actions, protocol |
StreamDeckPluginSharp.TypeGen (sdps-typegen) |
C# → TypeScript contracts |
@mikanseilaboratory/streamdeck-pi-client |
React hooks for inspectors (shared with StreamDeckPluginRust) |
dotnet test StreamDeckPluginSharp.sln
./pack.sh # or pack.ps1 on WindowsGitHub Actions runs tests on every push/PR and uploads NuGet packages and the sample .streamDeckPlugin. Tag v* to create a GitHub Release and publish to nuget.org via OIDC Trusted Publishing.
Do not store a long-lived NUGET_API_KEY.
One-time nuget.org setup:
- nuget.org → account menu → Trusted Publishing → add a policy:
- Repository owner:
MikanseiLaboratory - Repository:
StreamDeckPluginSharp - Workflow file:
release.yml(file name only)
- Repository owner:
- In this GitHub repo, add Actions variable
NUGET_USERset to your nuget.org profile name (not email).
The Property Inspector client is published from streamdeck-pi-client.
Apache License 2.0