Skip to main content

What is a Bot?

A bot in Botpress is a conversational application that processes messages, manages state, and executes actions. Bots are composed of:
  • Definition: Structure and capabilities (states, events, actions, configuration)
  • Implementation: Runtime logic (handlers, business logic)
  • Dependencies: Integrations and plugins that extend functionality

Creating a Bot

Bot Definition

The bot definition describes what your bot can do. From packages/sdk/src/bot/definition.ts:197:
import { BotDefinition } from '@botpress/sdk'
import { z } from '@botpress/sdk'

const bot = new BotDefinition({
  // Bot configuration schema
  configuration: {
    schema: z.object({
      apiKey: z.string().describe('API Key for external service'),
      welcomeMessage: z.string().default('Hello!')
    })
  },
  
  // State definitions
  states: {
    userProfile: {
      type: 'user',
      schema: z.object({
        name: z.string(),
        email: z.string().email(),
        preferences: z.object({
          language: z.string(),
          notifications: z.boolean()
        })
      })
    },
    conversationTopic: {
      type: 'conversation',
      schema: z.object({
        currentTopic: z.string(),
        history: z.array(z.string())
      })
    }
  },
  
  // Custom events
  events: {
    userSubscribed: {
      schema: z.object({
        userId: z.string(),
        plan: z.enum(['free', 'pro', 'enterprise'])
      })
    }
  },
  
  // Custom actions
  actions: {
    sendEmail: {
      title: 'Send Email',
      description: 'Send an email to a user',
      input: {
        schema: z.object({
          to: z.string().email(),
          subject: z.string(),
          body: z.string()
        })
      },
      output: {
        schema: z.object({
          messageId: z.string(),
          sent: z.boolean()
        })
      }
    }
  },
  
  // User tags for metadata
  user: {
    tags: {
      customerId: { title: 'Customer ID' },
      tier: { title: 'Subscription Tier' }
    }
  },
  
  // Conversation tags
  conversation: {
    tags: {
      channel: { title: 'Origin Channel' },
      priority: { title: 'Priority Level' }
    }
  }
})

Bot Implementation

The implementation contains the runtime handlers. From packages/sdk/src/bot/implementation.ts:43:
import { BotImplementation } from '@botpress/sdk'

const botImpl = new BotImplementation({
  // Register handler - runs on bot startup
  register: async ({ client, ctx }) => {
    console.log('Bot registered successfully')
    // Initialize resources, connect to services, etc.
  },
  
  // Action implementations
  actions: {
    sendEmail: async ({ input, client, ctx }) => {
      // Implement the sendEmail action
      const result = await emailService.send({
        to: input.to,
        subject: input.subject,
        body: input.body
      })
      
      return {
        messageId: result.id,
        sent: result.success
      }
    }
  },
  
  plugins: {} // Plugin implementations
})

Message Handlers

Handle incoming messages from users:
// Handle text messages
botImpl.on.message('text', async ({ message, client, ctx }) => {
  const text = message.payload.text
  
  // Access conversation state
  const { state } = await client.getState({
    type: 'conversation',
    id: ctx.conversationId,
    name: 'conversationTopic'
  })
  
  // Process message
  if (text.includes('help')) {
    await client.createMessage({
      conversationId: ctx.conversationId,
      userId: ctx.botId,
      type: 'text',
      payload: { text: 'How can I help you?' }
    })
  }
  
  // Update state
  await client.setState({
    type: 'conversation',
    id: ctx.conversationId,
    name: 'conversationTopic',
    payload: {
      currentTopic: 'help',
      history: [...state.history, 'help']
    }
  })
})

// Handle all message types with wildcard
botImpl.on.message('*', async ({ message, client }) => {
  console.log('Received message:', message.type)
})
Message handlers are executed in order of registration. Use '*' as a catch-all handler that runs for every message type.

Event Handlers

React to custom events:
botImpl.on.event('userSubscribed', async ({ event, client, ctx }) => {
  const { userId, plan } = event.payload
  
  // Update user tags
  await client.updateUser({
    id: userId,
    tags: {
      tier: plan
    }
  })
  
  // Send welcome message based on plan
  const welcomeMessages = {
    free: 'Welcome to the free plan!',
    pro: 'Welcome to Pro! Enjoy premium features.',
    enterprise: 'Welcome to Enterprise! Your dedicated support team is ready.'
  }
  
  await client.createMessage({
    conversationId: ctx.conversationId,
    userId: ctx.botId,
    type: 'text',
    payload: { text: welcomeMessages[plan] }
  })
})

Hooks

Hooks allow you to intercept and modify data at various lifecycle points. From packages/sdk/src/bot/implementation.ts:310:
// Before incoming message (preprocessing)
botImpl.on.beforeIncomingMessage('text', async ({ message }) => {
  // Sanitize or transform message
  message.payload.text = message.payload.text.toLowerCase().trim()
})

// After outgoing message (logging)
botImpl.on.afterOutgoingMessage('*', async ({ message }) => {
  console.log('Sent message:', message.id)
  // Log to analytics, audit trail, etc.
})

// Before action execution
botImpl.on.beforeOutgoingCallAction('sendEmail', async ({ input }) => {
  // Validate, rate limit, etc.
  if (!input.to.includes('@')) {
    throw new Error('Invalid email address')
  }
})

// After event processing
botImpl.on.afterIncomingEvent('userSubscribed', async ({ event }) => {
  // Trigger side effects
  await analytics.track('subscription_started', event.payload)
})

Available Hooks

  • beforeIncomingMessage / afterIncomingMessage
  • beforeOutgoingMessage / afterOutgoingMessage
  • beforeIncomingEvent / afterIncomingEvent
  • beforeOutgoingCallAction / afterOutgoingCallAction
  • beforeIncomingCallAction / afterIncomingCallAction (experimental)

Adding Integrations

Integrations connect your bot to external platforms:
import telegram from '@botpress/telegram'
import slack from '@botpress/slack'

// Add Telegram integration
bot.addIntegration(telegram, {
  alias: 'tg',
  configuration: {
    botToken: process.env.TELEGRAM_BOT_TOKEN
  }
})

// Add Slack integration
bot.addIntegration(slack, {
  alias: 'slackMain',
  configuration: {
    botToken: process.env.SLACK_BOT_TOKEN
  },
  disabledChannels: ['dm'] // Optionally disable specific channels
})
From packages/sdk/src/bot/definition.ts:258, integrations can be aliased to distinguish between multiple instances.
You can add multiple instances of the same integration with different aliases and configurations. For example, separate Slack integrations for different workspaces.

Adding Plugins

Plugins provide reusable functionality:
import analytics from '@botpress/analytics'
import anthropic from '@botpress/anthropic'

// First, add the integration that provides the interface
bot.addIntegration(anthropic, {
  alias: 'llm',
  configuration: {
    apiKey: process.env.ANTHROPIC_API_KEY
  }
})

// Then add the plugin and wire its dependencies
bot.addPlugin(analytics, {
  alias: 'analytics',
  configuration: {},
  dependencies: {
    // Map plugin's interface dependency to integration
    llm: {
      integrationAlias: 'llm',
      integrationInterfaceAlias: 'llm'
    }
  }
})
See Plugins for more details on how plugins work.

State Management

Botpress provides four types of state:
Persistent state tied to a specific user across all conversations:
const { state } = await client.getState({
  type: 'user',
  id: ctx.userId,
  name: 'userProfile'
})

await client.setState({
  type: 'user',
  id: ctx.userId,
  name: 'userProfile',
  payload: { 
    name: 'John Doe',
    email: 'john@example.com'
  }
})
State for a specific conversation:
const { state } = await client.getState({
  type: 'conversation',
  id: ctx.conversationId,
  name: 'conversationTopic'
})
Global state shared across the entire bot:
const { state } = await client.getState({
  type: 'bot',
  id: ctx.botId,
  name: 'globalConfig'
})
Temporary state during workflow execution (experimental):
const { state } = await client.getState({
  type: 'workflow',
  id: workflowId,
  name: 'workflowData'
})

State Expiry

You can set automatic expiry for states:
const bot = new BotDefinition({
  states: {
    tempSession: {
      type: 'conversation',
      expiry: 3600, // Expires after 1 hour (in seconds)
      schema: z.object({ sessionId: z.string() })
    }
  }
})

// Handle expired state
botImpl.on.stateExpired('tempSession', async ({ state }) => {
  console.log('Session expired:', state.payload.sessionId)
  // Clean up, notify user, etc.
})

Tables

Store structured data in custom tables:
const bot = new BotDefinition({
  tables: {
    orders: {
      schema: z.object({
        orderId: z.string(),
        userId: z.string(),
        items: z.array(z.object({
          productId: z.string(),
          quantity: z.number()
        })),
        total: z.number(),
        status: z.enum(['pending', 'shipped', 'delivered'])
      }),
      indexes: ['userId', 'status']
    }
  }
})

// Use in handlers
const { rows } = await client.listTableRows({
  table: 'orders',
  filter: { userId: ctx.userId }
})

Workflows (Experimental)

Workflows are long-running processes that can pause and resume:
const bot = new BotDefinition({
  workflows: {
    orderFulfillment: {
      title: 'Order Fulfillment',
      input: {
        schema: z.object({
          orderId: z.string()
        })
      },
      output: {
        schema: z.object({
          status: z.string()
        })
      }
    }
  }
})

botImpl.on.workflowStart('orderFulfillment', async ({ workflow, client }) => {
  console.log('Order workflow started:', workflow.input.orderId)
})

botImpl.on.workflowTimeout('orderFulfillment', async ({ workflow }) => {
  console.log('Order workflow timed out:', workflow.id)
})

Starting Your Bot

Cloud Deployment

bp deploy
Deploying to Botpress Cloud provides:
  • Automatic scaling
  • Built-in state management
  • Integration with Botpress Studio
  • Analytics and monitoring
  • Webhook management

Local Development

import { serve } from '@botpress/sdk'

// Start local HTTP server
const server = await botImpl.start(8072)
console.log('Bot running on port 8072')

Best Practices

Separate Concerns

Keep definition (structure) and implementation (logic) separate for better organization and testing.

Use Type Safety

Leverage TypeScript and Zod schemas for compile-time and runtime validation.

Handle Errors

Always handle errors gracefully in handlers. Errors bubble up to Botpress Cloud for logging.

State Management

Choose the right state scope (bot/user/conversation/workflow) based on your data’s lifecycle.

Next Steps

Integrations

Learn how to connect your bot to external platforms

Plugins

Extend your bot with reusable plugins

SDK Reference

Explore the complete SDK API

Examples

See complete bot examples

Build docs developers (and LLMs) love