What is a Flow?
In BuilderBot, a flow represents a conversation path or dialogue structure. The FlowClass manages the flow system, handling flow manipulation, creating callback indices, and enabling keyword-based navigation through conversations.
Creating Flows
Flows are created using the createFlow function, which accepts an array of flow definitions:
import { createFlow, addKeyword } from '@builderbot/bot'
const welcomeFlow = addKeyword(['hi', 'hello'])
.addAnswer('Welcome to our chatbot!')
const helpFlow = addKeyword('help')
.addAnswer('How can I help you?')
const adapterFlow = createFlow([welcomeFlow, helpFlow])
FlowClass Architecture
The FlowClass is the core engine that manages all flows in your bot. It provides several key capabilities:
Internal Structure
class FlowClass {
allCallbacks: Record<string, Function>
flowSerialize: TContext[]
flowRaw: TFlow[]
}
Key properties:
allCallbacks - Index of all callback functions across flows
flowSerialize - Serialized representation of flows for efficient matching
flowRaw - Original flow definitions
Flow Matching
The find() method is the heart of flow matching, supporting multiple matching strategies:
find(keyOrWord: string, symbol: boolean = false, overFlow: TContext[] | null = null): TContext[]
Keyword Matching
When a user message arrives, the flow system searches for matching keywords using case-insensitive or case-sensitive matching.
Symbol Resolution
If symbol is true, it performs direct reference lookup by flow ID rather than text matching.
Nested Flow Discovery
The system recursively follows flow references to build the complete conversation path.
Matching Options
The flow system supports sophisticated matching through options:
Case Sensitivity:
const flow = addKeyword('HELLO', { sensitive: true })
// Only matches "HELLO" exactly
Regex Patterns:
const flow = addKeyword('/^\d{3,}$/', { regex: true })
// Matches any message with 3+ digits
Multiple Keywords:
const flow = addKeyword(['hi', 'hello', 'hey'])
// Matches any of the keywords
Flow Serialization
When flows are created, they’re serialized into an optimized format for fast lookup:
const mergeToJsonSerialize = _flow.map((flowItem) => flowItem.toJson()).flat(2)
this.flowSerialize = toSerialize(mergeToJsonSerialize)
This serialization:
- Flattens nested flow structures
- Creates unique references for each step
- Builds an index for O(1) callback lookups
Flow Methods
Finding Flows
The FlowClass provides multiple methods to locate flows:
By Serialize
By Ref
By Keyword
findBySerialize(refSerialize: string, k: number = 0): TContext | undefined
Find a flow by its serialized reference, with optional offset.findSerializeByRef(ref: string): TContext | undefined
Locate a flow using its unique reference ID.findSerializeByKeyword(keyword: string): TContext | undefined
Search for a flow by its trigger keyword.
Nested Flows
Access child flows within a parent flow:
getFlowsChild(): TContext[]
This method:
- Extracts all nested flows from the main flow structure
- Flattens the hierarchy for easy iteration
- Filters out undefined references
Flow Structure (TFlow)
Every flow object implements the TFlow interface:
interface TFlow<P = any, B = any> {
ctx: TContext // Flow context
ref: string // Unique reference
addAnswer: (...) => TFlow<P, B> // Add response
addAction: (...) => TFlow<P, B> // Add callback
toJson: () => TContext[] // Serialize
}
Practical Example
Here’s a complete flow demonstrating key concepts:
import { createFlow, addKeyword } from '@builderbot/bot'
// Main welcome flow with nested options
const welcomeFlow = addKeyword(['hi', 'hello', 'hola'])
.addAnswer('Welcome! How can I help you today?')
.addAnswer(
'Choose an option:\n1. Documentation\n2. Support\n3. Pricing',
{ capture: true },
async (ctx, { gotoFlow }) => {
if (ctx.body.includes('1')) {
return gotoFlow(docFlow)
}
if (ctx.body.includes('2')) {
return gotoFlow(supportFlow)
}
if (ctx.body.includes('3')) {
return gotoFlow(pricingFlow)
}
},
[docFlow, supportFlow, pricingFlow] // Nested flows
)
const docFlow = addKeyword('doc')
.addAnswer('Documentation: https://builderbot.app/docs')
const supportFlow = addKeyword('support')
.addAnswer('Contact support: [email protected]')
const pricingFlow = addKeyword('pricing')
.addAnswer('View pricing: https://builderbot.app/pricing')
const adapterFlow = createFlow([
welcomeFlow,
docFlow,
supportFlow,
pricingFlow
])
Best Practices
Flow Organization: Keep flows focused on specific conversation topics. Use nested flows for sub-conversations.
Array Requirement: The createFlow function requires an array of flows. Passing a single flow without an array wrapper will throw an error.
Performance: The flow system is optimized for fast keyword matching. Even with hundreds of flows, lookups remain efficient through the serialization index.
Error Handling
The FlowClass validates inputs during construction:
if (!Array.isArray(_flow)) throw new Error('Must be an array of flows')
Always wrap flows in an array when creating the flow adapter:
// ✅ Correct
const adapterFlow = createFlow([flow1, flow2])
// ❌ Wrong - will throw error
const adapterFlow = createFlow(flow1)