@fcannizzaro/streamdeck-react

Manifest Generation

How manifest.json is auto-generated from defineAction() calls and bundler plugin config.

manifest.json is auto-generated at build time. You don't need to write or maintain it by hand.

How It Works

The bundler plugin (streamDeckReact()) combines two sources of information:

  1. Plugin metadata from the manifest option in your Vite config.
  2. Action metadata auto-extracted from defineAction({ info }) calls in your source code via AST analysis.
defineAction({ uuid, key, info: { name, icon } })
         ↓  build time
    moduleParsed hook → parse AST → extract info

    writeBundle → merge with plugin manifest → validate → write manifest.json

The generated manifest.json is written to your .sdPlugin directory alongside the bundled output.

Plugin Metadata

Provide plugin-level info in the bundler plugin's manifest option:

streamDeckReact({
  manifest: {
    uuid: "com.example.my-plugin",
    name: "My Plugin",
    author: "Your Name",
    description: "A Stream Deck plugin built with React.",
    icon: "imgs/plugin-icon",
    version: "0.0.0.1",
  },
});

PluginManifestInfo

FieldRequiredDescription
uuidYesPlugin UUID in reverse-DNS format.
nameYesPlugin display name.
authorYesAuthor name shown on Marketplace.
descriptionYesPlugin description.
iconYesPath to plugin icon (extension omitted).
versionYesPlugin version (e.g. "1.0.0.0").
categoryNoActions list group name. Default: same as name.
categoryIconNoCategory icon path. Default: same as icon.
urlNoPlugin website URL.
supportUrlNoSupport website URL.
propertyInspectorPathNoGlobal property inspector HTML path.
profilesNoPre-defined profiles distributed with the plugin.
applicationsToMonitorNoApplications to monitor on Mac/Windows.
defaultWindowSizeNoDefault window size for PI window.open().
codePathNoPlugin entry point. Default: derived from output.
codePathMacNomacOS-specific entry point override.
codePathWinNoWindows-specific entry point override.
osNoOS requirements. Default: mac 13+ and windows 10+.
nodejsNoNode.js config. Default: { version: "24" }.
sdkVersionNoSDK version. Default: 2.
softwareNoSoftware requirements. Default: "7.1".

Action Metadata

Each action's manifest entry comes from the info field on defineAction():

export const counterAction = defineAction({
  uuid: "com.example.my-plugin.counter",
  key: CounterKey,
  info: {
    name: "Counter",
    icon: "imgs/actions/counter",
  },
});

ActionManifestInfo

FieldRequiredDescription
nameYesAction display name in Stream Deck's action list.
iconYesPath to action icon (extension omitted).
disabledNoSkip this action from manifest generation. Default: false.
tooltipNoHover tooltip in the actions list.
statesNoCustom states. Default: [{ image: icon }].
encoderNoEncoder config (layout, triggerDescription, background).
disableAutomaticStatesNoDisable automatic state toggling.
disableCachingNoDisable Stream Deck image caching.
supportedInMultiActionsNoAvailable in multi-actions. Default: true.
supportedInKeyLogicActionsNoAvailable in key logic actions (SD 7.0+). Default: true.
visibleInActionsListNoVisible in the actions list. Default: true.
userTitleEnabledNoAllow user to edit title. Default: true.
propertyInspectorPathNoAction-specific property inspector HTML path.
supportUrlNoAction support URL.
osNoOS restriction for this action.
controllersNoController types. Auto-derived (see below).

Controllers Auto-Derivation

You don't need to specify controllers manually. The bundler plugin infers them from the components defined on each action:

  • key present → includes "Keypad"
  • dial or touchStrip present → includes "Encoder"
  • Both key and dial/touchStrip["Keypad", "Encoder"]
  • Neither → defaults to ["Keypad"]

If you explicitly set controllers on info, it overrides the auto-derivation.

Encoder Info

For encoder actions, include info.encoder with the layout and trigger descriptions:

export const volumeAction = defineAction({
  uuid: "com.example.my-plugin.volume",
  key: VolumeKey,
  dial: VolumeDial,
  info: {
    name: "Volume",
    icon: "imgs/actions/volume",
    encoder: {
      layout: "$A0",
      triggerDescription: {
        rotate: "Adjust volume",
        push: "Mute / Unmute",
      },
    },
  },
});

The encoder block maps to the Elgato manifest's Encoder field. Available options:

FieldDescription
layoutPre-defined ($X1, $A0, $A1, $B1, etc.) or custom .json path.
iconEncoder icon path (extension omitted).
stackColorBackground color for dial stack (hex).
backgroundTouchscreen background image (extension omitted).
triggerDescriptionDescriptions for rotate, push, touch, longTouch.

Disabled Actions

Set info.disabled: true to exclude an action from the generated manifest while keeping it functional at runtime. This is useful for debug or development-only actions:

export const debugAction = defineAction({
  uuid: "com.example.my-plugin.debug",
  key: DebugKey,
  info: {
    name: "Debug",
    icon: "imgs/actions/debug",
    disabled: true,
  },
});

States

When states is not provided, a single default state is generated using the icon field:

"States": [{ "Image": "imgs/actions/counter" }]

For custom states (e.g., multi-state toggles):

info: {
  name: "Toggle",
  icon: "imgs/actions/toggle",
  states: [
    { image: "imgs/actions/toggle-off", name: "Off" },
    { image: "imgs/actions/toggle-on", name: "On" },
  ],
}

Validation

The bundler plugin validates the manifest at build time:

  1. UUID format — The plugin UUID must be a valid reverse-DNS identifier.
  2. UUID prefix — Every action UUID must start with the plugin UUID prefix (e.g., "com.example.my-plugin.").
  3. Duplicate UUIDs — No two actions can share the same UUID.
  4. Required fields — Every action must have info.name and info.icon.
  5. Static values — All info values must be static literals (no variable references or computed values). The AST extractor needs to evaluate them at build time.

Validation errors are reported as build warnings.

Skip Behavior

The manifest file is only written when its content has changed. This avoids unnecessary rebuilds when using vite build --watch.

On this page