Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/botpress/botpress/llms.txt

Use this file to discover all available pages before exploring further.

BotHandlers define the runtime behavior of your bot by responding to messages, events, state changes, and lifecycle hooks.

Handler Types

Botpress bots support several types of handlers:
  • Message Handlers - Respond to incoming messages
  • Event Handlers - Process custom and integration events
  • State Expiry Handlers - Handle expired state
  • Action Handlers - Implement bot actions
  • Hook Handlers - Intercept operations before/after execution
  • Workflow Handlers - EXPERIMENTAL - Manage workflow lifecycle

Handler Props

All handlers receive a props object with common properties:

CommonHandlerProps

ctx
BotContext
Bot context containing configuration and IDs.
{
  botId: string
  type: string
  operation: BotOperation
  configuration: {
    payload: string // JSON-encoded configuration
  }
}
client
BotSpecificClient
Type-safe API client for bot operations.
logger
BotLogger
Logger instance for the bot.
logger.forBot().info('Message')
logger.forBot().error('Error', error)
logger.forBot().debug('Debug info')
logger.forBot().warn('Warning')

InjectedHandlerProps

Additional properties automatically injected:
workflows
WorkflowProxy
EXPERIMENTAL - Workflow management API.
workflows.create({
  name: 'onboarding',
  input: { userId: user.id }
})

Message Handlers

Handle incoming messages from channels.

MessagePayloads

type MessagePayloads<TBot> = {
  [MessageType in keyof Messages]: {
    message: Message & {
      type: MessageType
      payload: Messages[MessageType]
    }
    user: User
    conversation: Conversation
    event: Event
    
    // Common props
    ctx: BotContext
    client: BotSpecificClient
    logger: BotLogger
    workflows: WorkflowProxy
  }
}

MessageHandlers

type MessageHandlers<TBot> = {
  [MessageType in keyof Messages]: (
    props: MessagePayloads[MessageType]
  ) => Promise<void>
}

Example

bot.message('text', async (props) => {
  const { message, user, conversation, client, logger } = props
  
  logger.forBot().info('Text message:', message.payload.text)
  
  await client.createMessage({
    conversationId: conversation.id,
    userId: user.id,
    type: 'text',
    payload: { text: 'Reply' }
  })
})

// Handle all message types
bot.message('*', async ({ message, logger }) => {
  logger.forBot().debug('Message received:', message.type)
})

Event Handlers

Process custom and integration events.

EventPayloads

type EventPayloads<TBot> = {
  [EventType in keyof Events]: {
    event: Event & {
      type: EventType
      payload: Events[EventType]
    }
    
    // Common props
    ctx: BotContext
    client: BotSpecificClient
    logger: BotLogger
    workflows: WorkflowProxy
  }
}

EventHandlers

type EventHandlers<TBot> = {
  [EventType in keyof Events]: (
    props: EventPayloads[EventType]
  ) => Promise<void>
}

Example

bot.event('orderPlaced', async ({ event, client, logger }) => {
  logger.forBot().info('Order placed:', event.payload.orderId)
  
  await client.createEvent({
    type: 'orderProcessed',
    payload: {
      orderId: event.payload.orderId,
      processedAt: new Date().toISOString()
    }
  })
})

// Handle integration events
bot.event('github:issueOpened', async ({ event, client }) => {
  const issue = event.payload
  // Process GitHub issue
})

// Handle all events
bot.event('*', async ({ event, logger }) => {
  logger.forBot().debug('Event:', event.type)
})

State Expiry Handlers

Handle state expiration events.

StateExpiredPayloads

type StateExpiredPayloads<TBot> = {
  [StateName in keyof States]: {
    state: State & {
      name: StateName
      type: States[StateName]['type']
      payload: States[StateName]['payload']
    }
    
    // Common props
    ctx: BotContext
    client: BotSpecificClient
    logger: BotLogger
    workflows: WorkflowProxy
  }
}

Example

bot.stateExpired('userSession', async ({ state, client, logger }) => {
  logger.forBot().info('Session expired:', state.id)
  
  // Notify user or clean up
})

Action Handlers

Implement bot actions.

ActionHandlerPayloads

type ActionHandlerPayloads<TBot> = {
  [ActionName in keyof Actions]: {
    type?: ActionName
    input: Actions[ActionName]['input']
    
    // Common props
    ctx: BotContext
    client: BotSpecificClient
    logger: BotLogger
    workflows: WorkflowProxy
  }
}

ActionHandlers

type ActionHandlers<TBot> = {
  [ActionName in keyof Actions]: (
    props: ActionHandlerPayloads[ActionName]
  ) => Promise<Actions[ActionName]['output']>
}

Example

actions: {
  createTicket: async ({ input, client, logger }) => {
    logger.forBot().info('Creating ticket:', input.title)
    
    const ticket = await externalAPI.createTicket(input)
    
    return {
      ticketId: ticket.id,
      url: ticket.url
    }
  }
}

Hook Handlers

Intercept and modify operations.

Hook Types

type HookDefinitions = {
  before_incoming_event: { stoppable: true }
  before_incoming_message: { stoppable: true }
  before_outgoing_message: { stoppable: false }
  before_outgoing_call_action: { stoppable: false }
  before_incoming_call_action: { stoppable: false }
  after_incoming_event: { stoppable: true }
  after_incoming_message: { stoppable: true }
  after_outgoing_message: { stoppable: false }
  after_outgoing_call_action: { stoppable: false }
  after_incoming_call_action: { stoppable: false }
}

HookInputs

type HookInputs<THookType, TDataType> = {
  data: /* Event, Message, or Action data */
  
  // Common props
  ctx: BotContext
  client: BotSpecificClient
  logger: BotLogger
  workflows: WorkflowProxy
}

HookOutputs

type HookOutputs<THookType> = {
  data?: /* Modified data */
  stop?: boolean // Only for stoppable hooks
}

Before Hooks

// Intercept incoming events
bot.beforeIncomingEvent('orderPlaced', async ({ data, logger }) => {
  logger.forBot().info('Processing order event')
  
  // Modify event data
  return {
    data: {
      ...data,
      payload: {
        ...data.payload,
        timestamp: new Date().toISOString()
      }
    }
  }
})

// Stop processing based on condition
bot.beforeIncomingMessage('text', async ({ data }) => {
  if (data.payload.text.includes('spam')) {
    return { stop: true }
  }
  return { data }
})

// Intercept outgoing messages
bot.beforeOutgoingMessage('*', async ({ data }) => {
  return {
    data: {
      ...data,
      tags: {
        ...data.tags,
        sentBy: 'bot'
      }
    }
  }
})

// Intercept action calls
bot.beforeOutgoingCallAction('github:createIssue', async ({ data, logger }) => {
  logger.forBot().info('Creating GitHub issue')
  return { data }
})

After Hooks

// Process after event received
bot.afterIncomingEvent('*', async ({ data, client }) => {
  await client.trackAnalytics({
    event: 'event_received',
    properties: { type: data.type }
  })
  return { data }
})

// Process after message received
bot.afterIncomingMessage('*', async ({ data, logger }) => {
  logger.forBot().info('Message processed:', data.type)
  return { data }
})

// Process after message sent
bot.afterOutgoingMessage('*', async ({ data, logger }) => {
  logger.forBot().debug('Message sent:', data.message.id)
  return { data }
})

// Process after action called
bot.afterOutgoingCallAction('*', async ({ data, client }) => {
  // Log action results
  return { data }
})

Workflow Handlers

EXPERIMENTAL - Handle workflow lifecycle.

WorkflowPayloads

type WorkflowPayloads<TWorkflowName> = {
  conversation?: Conversation
  user?: User
  event: WorkflowUpdateEvent
  workflow: ActionableWorkflow<TWorkflowName>
  
  // Common props
  ctx: BotContext
  client: BotSpecificClient
  logger: BotLogger
  workflows: WorkflowProxy
}

WorkflowHandlers

type WorkflowHandlers<TBot> = {
  [WorkflowName in keyof Workflows]: (
    props: WorkflowPayloads[WorkflowName]
  ) => Promise<void>
}

Example

bot.workflowStart('onboarding', async ({ workflow, client, logger }) => {
  logger.forBot().info('Onboarding started:', workflow.id)
  
  await workflow.update({
    output: { currentStep: 'welcome' }
  })
})

bot.workflowContinue('onboarding', async ({ workflow, client }) => {
  // Handle workflow continuation
})

bot.workflowTimeout('onboarding', async ({ workflow, logger }) => {
  logger.forBot().warn('Onboarding timed out:', workflow.id)
})

Handler Registration Order

Handlers are executed in registration order:
bot.message('text', async ({ logger }) => {
  logger.forBot().info('Handler 1')
})

bot.message('text', async ({ logger }) => {
  logger.forBot().info('Handler 2')
})

// When a text message arrives:
// > Handler 1
// > Handler 2

Wildcard Handlers

Use '*' to handle all types:
// Handle all message types
bot.message('*', async ({ message }) => {
  // Runs for every message
})

// Handle all events
bot.event('*', async ({ event }) => {
  // Runs for every event
})

// Specific handler also runs
bot.message('text', async ({ message }) => {
  // Runs only for text messages
})

// Execution order for text message:
// 1. Specific 'text' handlers
// 2. Wildcard '*' handlers

Error Handling

Handle errors in handlers:
import { RuntimeError } from '@botpress/sdk'

bot.message('text', async ({ message, client, logger }) => {
  try {
    await client.callAction({
      type: 'externalAPI:process',
      input: { data: message.payload.text }
    })
  } catch (error) {
    logger.forBot().error('Action failed:', error)
    
    if (error instanceof RuntimeError) {
      // Handle SDK errors
    }
    
    // Optionally notify user
    await client.createMessage({
      conversationId: message.conversationId,
      userId: message.userId,
      type: 'text',
      payload: {
        text: 'Sorry, something went wrong.'
      }
    })
  }
})

See Also