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
andmeta
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, '<')
.replace(/>/g, '>')
}
}
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
- Always return the block - Even if you don't modify it
- Check block type - Use
BOTUI_BLOCK_TYPES
constants - Be defensive - Check if properties exist before accessing them
- Don't mutate unnecessarily - Only modify what you need to change
- Use meaningful names - Make your plugin's purpose clear
- 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.