Quick Start
Build your first Stream Deck plugin with @fcannizzaro/streamdeck-react.
This guide creates a minimal counter action, bundles it with Rollup, and connects it to the Stream Deck runtime.
1. Create a Plugin Project
Start with a normal Stream Deck plugin folder that contains a manifest.json and a plugin entry file.
Your action UUID in manifest.json must match the UUID you register in defineAction().
2. Install Dependencies
npm install @fcannizzaro/streamdeck-react react
npm install -D rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-json rollup-plugin-esbuild3. Create an Action Component
// src/actions/counter.tsx
import { useState } from 'react';
import { defineAction, useKeyDown, useKeyUp, tw } from '@fcannizzaro/streamdeck-react';
function CounterKey() {
const [count, setCount] = useState(0);
const [pressed, setPressed] = useState(false);
useKeyDown(() => {
setCount((value) => value + 1);
setPressed(true);
});
useKeyUp(() => {
setPressed(false);
});
return (
<div
className={tw(
'flex h-full w-full flex-col items-center justify-center gap-1',
pressed
? 'bg-linear-to-br from-[#2563eb] to-[#1d4ed8]'
: 'bg-linear-to-br from-[#0f172a] to-[#1e293b]',
)}
>
<span className="text-[12px] font-semibold uppercase tracking-[0.2em] text-white/70">
Count
</span>
<span className="text-[34px] font-black text-white">{count}</span>
</div>
);
}
export const counterAction = defineAction({
uuid: 'com.example.react-counter.counter',
key: CounterKey,
});4. Create the Plugin Entry
// src/plugin.ts
import { readFile } from 'node:fs/promises';
import { createPlugin } from '@fcannizzaro/streamdeck-react';
import { counterAction } from './actions/counter.tsx';
const plugin = createPlugin({
fonts: [
{
name: 'Inter',
data: await readFile(new URL('../fonts/Inter-Regular.ttf', import.meta.url)),
weight: 400,
style: 'normal',
},
],
actions: [counterAction],
});
await plugin.connect();5. Add a Rollup Config
// rollup.config.mjs
import { builtinModules } from 'node:module';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import esbuild from 'rollup-plugin-esbuild';
import { nativeAddon, resolveLibraryPaths } from '@fcannizzaro/streamdeck-react/rollup';
const PLUGIN_DIR = 'com.example.react-counter.sdPlugin';
const builtins = new Set(builtinModules.flatMap((id) => [id, `node:${id}`]));
export default {
input: 'src/plugin.ts',
output: {
file: `${PLUGIN_DIR}/bin/plugin.mjs`,
format: 'es',
sourcemap: true,
inlineDynamicImports: true,
},
external: (id) => builtins.has(id),
plugins: [
resolveLibraryPaths(),
resolve({ preferBuiltins: true }),
commonjs(),
json(),
esbuild({ target: 'node20', jsx: 'automatic' }),
nativeAddon(),
],
};6. Build
npx rollup -cInstall the generated .sdPlugin in the Stream Deck app, place the action on a key, and press it to see the counter update.
What's Happening
defineAction()maps your manifest UUID to a React component.createPlugin()prepares fonts, registers actions, and creates the shared runtime.plugin.connect()attaches to the Stream Deck SDK.- When the action appears,
@fcannizzaro/streamdeck-reactmounts a React root for that hardware instance. - State changes trigger re-renders, and the renderer pushes a new image to the device.