@fcannizzaro/streamdeck-react

Settings & Property Inspector

Bi-directional settings sync and Property Inspector communication.

Settings Sync Architecture

┌──────────────┐       setSettings()        ┌──────────────────┐
│  React Tree  │ ────────────────────────>   │  Stream Deck SDK │
│  (useState)  │                             │  (persisted)     │
│              │ <────────────────────────   │                  │
└──────────────┘   onDidReceiveSettings      └──────────────────┘

                                             ┌──────────────────┐
                                             │ Property Inspector│
                                             │ (HTML/JS UI)     │
                                             └──────────────────┘
  1. React to SDK: calling setSettings({ count: 5 }) updates the React state (triggers re-render) and calls action.setSettings() to persist.
  2. SDK to React: when the Property Inspector changes settings, onDidReceiveSettings fires and the React state updates.
  3. Conflict resolution: the SDK is the source of truth (last-write-wins).

Per-Action Settings

type MySettings = { color: string; brightness: number };

function MyKey() {
  const [settings, setSettings] = useSettings<MySettings>();

  useKeyDown(() => {
    setSettings({ brightness: Math.min(100, settings.brightness + 10) });
  });

  // settings.color, settings.brightness are reactive
}

setSettings uses shallow merge ({ ...current, ...partial }), matching the SDK behavior.

Global Settings

type GlobalConfig = { apiKey: string; theme: 'light' | 'dark' };

function MyKey() {
  const [global, setGlobal] = useGlobalSettings<GlobalConfig>();
  // Shared across all action instances
}

Property Inspector Communication

Beyond settings, you can send arbitrary messages between the plugin and the Property Inspector:

Sending to PI

function MyKey() {
  const sendToPI = useSendToPI();

  useKeyDown(() => {
    sendToPI({ currentState: 'active', timestamp: Date.now() });
  });
}

Receiving from PI

function MyKey() {
  usePropertyInspector<{ action: string }>((msg) => {
    if (msg.action === 'refresh') {
      // Handle PI message
    }
  });
}

Default Settings

Set default values in defineAction so new instances start with sensible state:

export const myAction = defineAction<MySettings>({
  uuid: 'com.example.plugin.my-action',
  key: MyKey,
  defaultSettings: { color: '#000', brightness: 50 },
});

On this page