@fcannizzaro/streamdeck-react

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-esbuild

3. 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 -c

Install 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

  1. defineAction() maps your manifest UUID to a React component.
  2. createPlugin() prepares fonts, registers actions, and creates the shared runtime.
  3. plugin.connect() attaches to the Stream Deck SDK.
  4. When the action appears, @fcannizzaro/streamdeck-react mounts a React root for that hardware instance.
  5. State changes trigger re-renders, and the renderer pushes a new image to the device.

Next Steps

On this page