Skip to main content

JavaScript and TypeScript Plugins

Default path

For a simple, fast, installable Stream Deck plugin, use the official Elgato SDK with TypeScript.

1. Prerequisites​

Current Elgato SDK 2.0.0 docs list:

  • Node.js 24 or newer.
  • Stream Deck app 7.1 or newer.
  • Stream Deck hardware, or Stream Deck Mobile for basic testing.
  • The Stream Deck CLI from npm.
  • A text editor with TypeScript support.

Install the CLI:

npm install -g @elgato/cli@latest
streamdeck -v

The CLI also has the shorthand alias sd.

2. Create a plugin​

streamdeck create

Use a reverse-DNS UUID:

com.yourcompany.timer
com.yourcompany.deployment-panel
de.example.obs-tools

Keep UUIDs lowercase and stable. Once users have actions in their profiles, changing plugin or action UUIDs breaks those assignments.

3. Generated project shape​

.
├── com.example.timer.sdPlugin/
│ ├── bin/
│ ├── imgs/
│ ├── logs/
│ ├── ui/
│ └── manifest.json
├── src/
│ ├── actions/
│ └── plugin.ts
├── package.json
├── rollup.config.mjs
└── tsconfig.json

The important split:

  • src/ contains source code.
  • *.sdPlugin/ is the compiled plugin directory Stream Deck loads.
  • manifest.json describes plugin metadata, actions, icons, supported OS versions, Node settings, and UI paths.
  • ui/ contains property inspector HTML.
  • imgs/ contains plugin/action imagery.
  • logs/ is used by the SDK logger.

4. Development loop​

npm run watch

The scaffolded watch script builds the plugin and restarts it in Stream Deck when source or manifest files change.

Manual loop:

npm run build
streamdeck restart com.example.timer

Useful CLI commands:

streamdeck link path/to/com.example.timer.sdPlugin
streamdeck list
streamdeck restart com.example.timer
streamdeck stop com.example.timer
streamdeck validate path/to/com.example.timer.sdPlugin
streamdeck pack path/to/com.example.timer.sdPlugin

5. Minimal action​

import { action, KeyDownEvent, SingletonAction, WillAppearEvent } from '@elgato/streamdeck';

type CounterSettings = {
count?: number;
};

@action({ UUID: 'com.example.timer.counter' })
export class CounterAction extends SingletonAction<CounterSettings> {
override async onWillAppear(ev: WillAppearEvent<CounterSettings>): Promise<void> {
await ev.action.setTitle(`${ev.payload.settings.count ?? 0}`);
}

override async onKeyDown(ev: KeyDownEvent<CounterSettings>): Promise<void> {
const count = (ev.payload.settings.count ?? 0) + 1;

await ev.action.setSettings({ count });
await ev.action.setTitle(`${count}`);
}
}

This pattern is enough for many simple plugins:

  1. Read settings from the event payload.
  2. Perform one short action.
  3. Persist changed settings.
  4. Update the visual state.

6. Register the action​

The action needs both implementation and manifest metadata.

{
"$schema": "https://schemas.elgato.com/streamdeck/plugins/manifest.json",
"Author": "Example",
"Name": "Timer Tools",
"Description": "Small Stream Deck timer utilities.",
"Version": "1.0.0.0",
"UUID": "com.example.timer",
"CodePath": "bin/plugin.js",
"SDKVersion": 2,
"Software": {
"MinimumVersion": "7.1"
},
"Nodejs": {
"Version": "24"
},
"OS": [
{ "Platform": "mac", "MinimumVersion": "13" },
{ "Platform": "windows", "MinimumVersion": "10" }
],
"Actions": [
{
"UUID": "com.example.timer.counter",
"Name": "Counter",
"Icon": "imgs/action-icon",
"States": [
{
"Image": "imgs/action-state"
}
]
}
]
}

Use the current schema URL in manifest.json for editor validation.

7. Common action events​

EventUse it for
onWillAppearInitialize title/image when action appears on a profile
onWillDisappearStop timers, unsubscribe, cleanup
onKeyDownRespond immediately to a key press
onKeyUpTrigger only after release, useful for click-like behavior
onDialRotateHandle Stream Deck + dial rotation
onDialPressHandle dial button press
onTouchTapHandle Stream Deck + touch strip interactions

8. Settings pattern​

Use settings for user configuration and small per-action state.

Good settings:

  • selected profile or target,
  • API base URL,
  • display mode,
  • count/toggle state,
  • non-secret IDs.

Bad settings:

  • long caches,
  • private tokens,
  • high-frequency telemetry,
  • generated files that change after packaging.

For secrets, use the operating system credential store, a local companion service, or an explicit user-managed configuration outside the distributable plugin.

9. Debugging​

Elgato's SDK supports Node.js debugging. In VS Code, attach to the Stream Deck plugin Node process while the plugin is running.

The manifest can configure Node debugging:

{
"Nodejs": {
"Version": "24",
"Debug": "enabled"
}
}

Use fixed ports only when necessary:

{
"Nodejs": {
"Version": "24",
"Debug": "--inspect=127.0.0.1:12345"
}
}

10. When JavaScript is enough​

Plain JavaScript is acceptable when:

  • the plugin has one or two actions,
  • the action settings are trivial,
  • no shared domain model exists,
  • you want the lowest possible friction.

11. When TypeScript is worth it​

Use TypeScript when:

  • actions share settings or payload models,
  • the plugin supports keys and dials,
  • a property inspector talks to the plugin,
  • async state matters,
  • users depend on stable behavior.

For new work, TypeScript is the better default.