| name | companion-actions |
| description | Reference for Bitfocus Companion module action definitions using @companion-module/base. Use when asked to add an action, implement a button command, define action options, or wire up a device control. Also use when user needs help with action callbacks, subscribe/unsubscribe lifecycle, or the CompanionActionDefinition API. |
Companion Actions Skill
When to Use This Skill
- User asks to "add an action", "create a button action", or "implement a command"
- User wants to add interactive controls that trigger device operations
- User needs help defining action options (textinput, number, dropdown, checkbox)
- User is implementing action callbacks or handling button press events
- User asks about subscribe/unsubscribe lifecycle for stateful actions
- User needs to update or re-register actions dynamically based on device state
Key API Types
CompanionActionDefinition
Defines a single action with its name, options, and callback.
Properties:
name: string — Human-readable name shown in the UI
description?: string — Optional help text
options: SomeCompanionActionInputField[] — User-configurable parameters
callback: (event: CompanionActionEvent) => Promise<void> | void — Executed when triggered
subscribe?: (event: CompanionActionEvent) => Promise<void> | void — Called when action is added to a button
unsubscribe?: (event: CompanionActionEvent) => Promise<void> | void — Called when action is removed
learn?: (event: CompanionActionEvent) => Promise<CompanionActionEventInfo | undefined> | CompanionActionEventInfo | undefined — For learning action parameters from device
SomeCompanionActionInputField
Union type covering all option field types: textinput, number, dropdown, checkbox, colorpicker, multidropdown, bonjourdevice, static-text, custom-variable.
Each field requires: id, type, label. Additional properties vary by type (e.g., min/max for number, choices for dropdown).
CompanionActionEvent
Passed to callbacks when action is triggered.
Properties:
options: { [id: string]: any } — User-configured values for all option fields
controlId: string — Unique ID of the control triggering the action
surfaceId: string | undefined — ID of the surface (hardware device) if applicable
Patterns & Examples
Basic Action Definition
import type { ModuleInstance } from './main.js'
export function UpdateActions(self: ModuleInstance): void {
self.setActionDefinitions({
sample_action: {
name: 'My First Action',
options: [
{
id: 'num',
type: 'number',
label: 'Test',
default: 5,
min: 0,
max: 100,
},
],
callback: async (event) => {
console.log('Hello world!', event.options.num)
},
},
})
}
Multiple Actions with Different Option Types
export function UpdateActions(self: ModuleInstance): void {
self.setActionDefinitions({
send_command: {
name: 'Send Command',
options: [
{
id: 'command',
type: 'dropdown',
label: 'Command',
choices: [
{ id: 'play', label: 'Play' },
{ id: 'stop', label: 'Stop' },
{ id: 'pause', label: 'Pause' },
],
default: 'play',
},
{
id: 'value',
type: 'textinput',
label: 'Value',
default: '',
},
],
callback: async (event) => {
const cmd = event.options.command as string
const val = event.options.value as string
await self.connection.sendCommand(cmd, val)
},
},
set_level: {
name: 'Set Level',
options: [
{
id: 'level',
type: 'number',
label: 'Level',
min: 0,
max: 100,
default: 50,
range: true,
},
],
callback: async (event) => {
const level = event.options.level as number
await self.connection.setLevel(level)
},
},
})
}
Using Subscribe/Unsubscribe for Stateful Actions
export function UpdateActions(self: ModuleInstance): void {
self.setActionDefinitions({
start_polling: {
name: 'Start Status Polling',
options: [],
subscribe: async (event) => {
self.startPolling(event.controlId)
},
unsubscribe: async (event) => {
self.stopPolling(event.controlId)
},
callback: async (event) => {
self.log('info', `Polling triggered from ${event.controlId}`)
},
},
})
}
Accessing Module State in Callbacks
export function UpdateActions(self: ModuleInstance): void {
self.setActionDefinitions({
toggle_mute: {
name: 'Toggle Mute',
options: [],
callback: async (event) => {
const currentMute = self.state.muted
const newMute = !currentMute
await self.connection.setMute(newMute)
self.state.muted = newMute
self.checkFeedbacks('is_muted')
},
},
})
}
Dynamic Action Re-registration
When device capabilities change (e.g., after connection or config update):
async function onDeviceConnected(self: ModuleInstance): Promise<void> {
const capabilities = await self.connection.getCapabilities()
self.deviceCapabilities = capabilities
UpdateActions(self)
}
Common Pitfalls
-
Forgetting to call setActionDefinitions on update
- Always call
UpdateActions(self) after module state changes that affect available actions
-
Not typing event.options correctly
- TypeScript doesn't enforce option types — cast them:
event.options.num as number
-
Blocking the callback
- Mark callbacks as
async and use await for device commands
- Don't use long synchronous operations
-
Mutating options directly
event.options is read-only — don't attempt to modify it
-
Not handling errors
- Wrap device calls in try/catch and log errors via
self.log('error', ...)
-
Error reporting from callbacks — both approaches are valid
- When a callback encounters an invalid input or unexpected state, either throwing or logging is acceptable:
if (!valid) throw new Error(`Invalid value: ${value}`)
if (!valid) {
self.log('error', `Invalid value: ${value}`)
return
}
- Both patterns are correct.
throw causes Companion to catch and surface the error; self.log('error', ...) + return reports it without raising an exception. Prefer whichever is consistent with the existing style in the module.
- Do not flag either approach as a defect in reviews.
Import Reference
import type {
CompanionActionDefinition,
CompanionActionDefinitions,
CompanionActionEvent,
CompanionActionContext,
SomeCompanionActionInputField,
} from '@companion-module/base'
Related Skills
- companion-config — For understanding option field types (they match config field types)
- companion-feedbacks — Actions often trigger feedback updates via
self.checkFeedbacks()
- companion-variables — Actions may update variable values via
self.setVariableValues()