Stream Kit Docs
API ReferenceCore

Run script

Execute custom TypeScript in an action handler chain with full app API access and typed trigger context.

Run script

Handler IDcore:core:script:run-script
PluginCore Handlers
CategoryScript

Run custom TypeScript inside an action handler chain. Scripts receive the full Stream Kit app API (same as plugins) and the trigger context for the current run. The in-app editor uses Monaco with TypeScript IntelliSense; use Open in editor to edit the script in Cursor or VS Code with auto-sync back to Stream Kit.

When to use

Use Run script when built-in handlers are not enough: custom logic, combining multiple plugin APIs, transforming trigger data, or setting action variables programmatically.

Script shape

Export a default async function that receives a single ScriptContext object:

import type { ScriptContext } from '@stream-kit/script-api';

export default async ({ app, context }: ScriptContext) => {
  const [{ trigger, data, actionVariables }] = context;

  await app.toast.create({
    title: `Triggered by ${trigger}`,
    variant: 'success'
  });
};
ParameterTypeDescription
appPluginAppApiFull application API — toast, filesystem, plugins, actions, queues, and more. See Plugin API.
contextHandlerTriggerContext[]Trigger payloads for this run (usually one entry).

context entries

Each item in context has:

FieldTypeDescription
triggerstringStable trigger ID (for example twitch:twitch:chat:chat-message).
dataTrigger-specificPayload from the trigger that fired. Typed in the editor based on triggers configured on the action.
actionVariablesRecord<string, string>Mutable action-scoped variables for this handler chain run. Changes are visible to later handlers.

You can also return a plain object from the script. Each key is written into actionVariables (values are stringified) so later handlers can use {key} placeholders.

Browse trigger payloads in the Triggers API reference.

app API overview

Scripts use the same app object as plugin handlers:

APIPurpose
app.toastShow notifications
app.confirmAsk the user to confirm
app.fsRead/write files
app.pluginsCall other plugins' public APIs
app.actionsRun or refresh actions
app.actionQueuesPause, resume, or inspect queues
app.commandsIntegrate with chat commands
app.oauthOAuth flows
app.openerOpen URLs or paths
app.processWatch or run programs
app.hotkeysGlobal shortcuts
app.localTtsLocal TTS runtime
app.dbPlugin database migrations
app.i18nTranslations

See the full reference in Plugin API.

Configuration

FieldTypeRequiredDescription
Scriptcode (TypeScript)YesScript body. Default template exports async ({ app, context }) => { … }.

Editor and types

  • In-app editor — Monaco with TypeScript checking and autocomplete for app, context, and trigger data (narrowed to triggers on the action).
  • Open in editor — Writes a small project under app data (scripts/actions/{actionId}/handlers/{handlerId}/) and opens it in Cursor or VS Code. File changes sync back to the handler field automatically.

Save the action first; Open in editor requires a persisted action ID.

Limits and errors

LimitValue
Execution timeout5 seconds
Error handlingFailures show an error toast; the handler chain continues

Thrown errors and timeouts do not stop the handler chain unless a later handler depends on side effects that did not run.

Examples

Show a toast with trigger data

import type { ScriptContext } from '@stream-kit/script-api';

export default async ({ app, context }: ScriptContext) => {
  const [{ data }] = context;
  const message = typeof data === 'object' && data && 'message' in data
    ? String((data as { message: unknown }).message)
    : 'Action ran';

  await app.toast.create({ title: message, variant: 'info' });
};

Set an action variable for later handlers

Mutate actionVariables in place, or return a plain object — both become {key} placeholders for later handlers. Objects/numbers are stringified.

import type { ScriptContext } from '@stream-kit/script-api';

export default async ({ context }: ScriptContext) => {
  const [{ data, actionVariables }] = context;

  if (actionVariables) {
    actionVariables.greeting = 'Hello from script';
  }

  // Equivalent: return values are merged into action variables
  return {
    normalizedMessage:
      typeof data === 'object' && data && 'message' in data
        ? String((data as { message: unknown }).message).toLowerCase()
        : ''
  };
};

Use {greeting} or {normalizedMessage} in subsequent handler fields.

Call a plugin API

import type { ScriptContext } from '@stream-kit/script-api';

export default async ({ app, context }: ScriptContext) => {
  const twitch = app.plugins.tryGet<{ sendMessage(channel: string, message: string): Promise<void> }>('twitch');
  const channel = typeof context[0]?.data === 'object' && context[0].data && 'channel' in context[0].data
    ? String((context[0].data as { channel: unknown }).channel)
    : '';

  if (twitch && channel) {
    await twitch.sendMessage(channel, 'Script says hi!');
  }
};

On this page