Skip to main content

Plugins

Plugins are the powerful extensibility system in BotUI that allow you to transform blocks as they flow through the system. They enable powerful customization without modifying core functionality.

Plugin Architecture

A plugin is a function that transforms blocks as they flow through the BotUI system. Every message and action passes through all registered plugins before being processed.

Plugin Signature

const plugin = (block: Block) => Block

A plugin:

  • Takes a Block as input
  • Returns a modified Block as output
  • Can modify both data and meta properties
  • Should always return the block (even if unchanged)

Basic Plugin Examples

Text Transformation Plugin

import { BOTUI_BLOCK_TYPES } from 'botui'

const emphasisPlugin = (block) => {
if (block.type === BOTUI_BLOCK_TYPES.MESSAGE) {
// Transform !(text) to <i>text</i>
block.data.text = block.data.text?.replace(/!\(([^\)]+)\)/g, '<i>$1</i>')

// Transform *text* to <strong>text</strong>
block.data.text = block.data.text?.replace(/\*([^\*]+)\*/g, '<strong>$1</strong>')
}
return block
}

myBot.use(emphasisPlugin)

Logging Plugin

const loggingPlugin = (block) => {
console.log(`[BotUI] ${block.type.toUpperCase()}:`, {
key: block.key,
data: block.data,
meta: block.meta
})
return block
}

myBot.use(loggingPlugin)

Content Filtering Plugin

const profanityFilterPlugin = (block) => {
if (block.type === BOTUI_BLOCK_TYPES.MESSAGE && block.data.text) {
const bannedWords = ['spam', 'inappropriate']
let text = block.data.text

bannedWords.forEach(word => {
const regex = new RegExp(word, 'gi')
text = text.replace(regex, '*'.repeat(word.length))
})

block.data.text = text
}
return block
}

myBot.use(profanityFilterPlugin)

Advanced Plugin Examples

Metadata Enhancement Plugin

const timestampPlugin = (block) => {
// Add timestamp to all blocks
block.meta.timestamp = new Date().toISOString()

// Add additional metadata for messages
if (block.type === BOTUI_BLOCK_TYPES.MESSAGE) {
block.meta.messageLength = block.data.text?.length || 0
block.meta.wordCount = block.data.text?.split(' ').length || 0
}

return block
}

myBot.use(timestampPlugin)

Conditional Processing Plugin

const markdownPlugin = (block) => {
// Only process if markdown is enabled in meta
if (block.meta.enableMarkdown && block.type === BOTUI_BLOCK_TYPES.MESSAGE) {
block.data.text = block.data.text
?.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') // **bold**
?.replace(/\*(.*?)\*/g, '<em>$1</em>') // *italic*
?.replace(/`(.*?)`/g, '<code>$1</code>') // `code`
}
return block
}

myBot.use(markdownPlugin)

// Usage with markdown enabled
await myBot.message.add(
{ text: 'This is **bold** and *italic* text with `code`' },
{ enableMarkdown: true }
)

Data Validation Plugin

const validationPlugin = (block) => {
if (block.type === BOTUI_BLOCK_TYPES.MESSAGE) {
// Ensure text exists
if (!block.data.text) {
block.data.text = '[Empty message]'
}

// Limit message length
const maxLength = block.meta.maxLength || 1000
if (block.data.text.length > maxLength) {
block.data.text = block.data.text.substring(0, maxLength - 3) + '...'
block.meta.truncated = true
}

// Sanitize HTML if not explicitly allowed
if (!block.meta.allowHtml) {
block.data.text = block.data.text
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
}
}
return block
}

myBot.use(validationPlugin)

Analytics Plugin

const analyticsPlugin = (block) => {
// Track message analytics
if (block.type === BOTUI_BLOCK_TYPES.MESSAGE) {
analytics.track('bot_message_sent', {
messageType: block.meta.messageType || 'text',
messageLength: block.data.text?.length || 0,
timestamp: new Date().toISOString()
})
}

// Track action analytics
if (block.type === BOTUI_BLOCK_TYPES.ACTION) {
analytics.track('bot_action_requested', {
actionType: block.meta.actionType || 'input',
hasOptions: !!block.data.options,
optionCount: block.data.options?.length || 0,
timestamp: new Date().toISOString()
})
}

return block
}

myBot.use(analyticsPlugin)

Dynamic Content Plugin

const dynamicContentPlugin = (block) => {
if (block.type === BOTUI_BLOCK_TYPES.MESSAGE && block.data.text) {
// Replace variables with dynamic content
block.data.text = block.data.text
.replace(/\{\{username\}\}/g, getCurrentUser()?.name || 'User')
.replace(/\{\{time\}\}/g, new Date().toLocaleTimeString())
.replace(/\{\{date\}\}/g, new Date().toLocaleDateString())

// Process conditional content
block.data.text = block.data.text.replace(
/\{\{if:(.*?)\}\}(.*?)\{\{\/if\}\}/g,
(match, condition, content) => {
return evaluateCondition(condition) ? content : ''
}
)
}
return block
}

function evaluateCondition(condition) {
// Simple condition evaluation - extend as needed
switch(condition) {
case 'authenticated': return !!getCurrentUser()
case 'mobile': return window.innerWidth < 768
default: return false
}
}

myBot.use(dynamicContentPlugin)

// Usage
await myBot.message.add({
text: 'Hello {{username}}! {{if:authenticated}}Welcome back!{{/if}}'
})

Plugin Registration and Execution

Registration

const myBot = createBot()

// Method 1: Individual registration
myBot.use(plugin1)
myBot.use(plugin2)
myBot.use(plugin3)

// Method 2: Chained registration
myBot
.use(plugin1)
.use(plugin2)
.use(plugin3)

Execution Order

Plugins execute in registration order. Each plugin receives the output of the previous plugin:

myBot.use(plugin1)  // Executes first
myBot.use(plugin2) // Receives output from plugin1
myBot.use(plugin3) // Receives output from plugin2

Example Flow

myBot.use(block => {
console.log('Plugin 1:', block.data.text) // "hello"
block.data.text = 'hola'
return block
})

myBot.use(block => {
console.log('Plugin 2:', block.data.text) // "hola" (modified by plugin 1)
block.data.text = 'hi'
return block
})

myBot.use(block => {
console.log('Plugin 3:', block.data.text) // "hi" (modified by plugin 2)
return block
})

await myBot.message.add({ text: 'hello' })

Plugin Best Practices

  1. Always return the block - Even if you don't modify it
  2. Check block type - Use BOTUI_BLOCK_TYPES constants
  3. Be defensive - Check if properties exist before accessing them
  4. Don't mutate unnecessarily - Only modify what you need to change
  5. Use meaningful names - Make your plugin's purpose clear
  6. Handle errors gracefully - Don't let plugins break the bot flow

Error Handling in Plugins

const safePlugin = (block) => {
try {
// Your plugin logic here
if (block.type === BOTUI_BLOCK_TYPES.MESSAGE) {
block.data.text = block.data.text?.toUpperCase()
}
return block
} catch (error) {
console.error('Plugin error:', error)
// Return original block if plugin fails
return block
}
}

myBot.use(safePlugin)

Testing Plugins

You can test plugins in isolation:

import { createBlock, BOTUI_BLOCK_TYPES } from 'botui'

// Create test block
const testBlock = createBlock(
BOTUI_BLOCK_TYPES.MESSAGE,
{ messageType: 'text' },
{ text: 'Hello !(world)!' }
)

// Test plugin
const result = emphasisPlugin(testBlock)
console.log(result.data.text) // "Hello <i>world</i>!"

TypeScript Support

For TypeScript users, plugins can be strongly typed:

import { Plugin, Block, BOTUI_BLOCK_TYPES } from 'botui'

const typedPlugin: Plugin = (block: Block): Block => {
if (block.type === BOTUI_BLOCK_TYPES.MESSAGE) {
// TypeScript will provide autocomplete and type checking
block.data.text = (block.data.text as string)?.toUpperCase()
}
return block
}

Plugins are one of BotUI's most powerful features, allowing you to customize every aspect of your bot's behavior while keeping your core conversation logic clean and focused.