Flow Control Nodes
Flow control nodes manage how your flow runs — asking questions, looping, and controlling the conversation pace.
Asking Questions
Ask Question (flow.ask)
Asks a question, validates the answer, and handles retries.
This is the primary way to get input from users.
| Setting | What It Does |
|---|---|
| Prompt | The question/message to send |
| Expect | Type of answer (text, number, photo, etc.) |
| Save to Variable | Where to store the valid answer |
| Validation | Optional expression (e.g. val > 10) |
| Retry Message | What to say if validation fails |
Supported Input Types:
text- Any textnumber- Validates it's a numberphoto/video/audio- Waits for mediachoice- Matches against a listregex- Matches a patterncallback- Waits for a button click
Outputs:
- ⚡ NEXT — Input is valid and saved
- ⚡ Failed — Max retries exceeded
- 📝 Value — The typed value (e.g. actual number 42, not "42")
Loops
For Each
Runs actions for each item in a list
Perfect for:
- Sending multiple messages
- Processing a list of users
- Going through products
| Setting | What It Does |
|---|---|
| Array | The list to loop through |
Outputs:
- ⚡ Each — Runs for every item
- 📦 Item — Current item
- 🔢 Index — Position (0, 1, 2...)
- ⚡ Done — After all items processed
Iterate (Rate-Limited)
Like For Each, but with delay between items
Use for:
- Broadcasting to many users (avoid rate limits)
- Processing large batches
| Setting | What It Does |
|---|---|
| Array | The list |
| Delay | Milliseconds between items |
Multi-Step Forms & Wizards
🧙♂️ Wizard (menu.wizard)
The all-in-one solution for multi-step forms, surveys, and onboarding flows.
Instead of chaining many Ask Question nodes manually, use one Wizard node:
Key Features:
- 📝 8 Input Types: text, number, email, phone, url, select, confirm, photo
- ✅ Built-in Validation: Email format, phone numbers, custom expressions
- 🔀 Conditional Steps: Show/hide steps based on previous answers
- 🌳 Branching: Jump to different steps dynamically
- 🔄 Retry Handling: Per-step retry limits with custom error messages
- 💾 Auto-Save: Collected data available as structured object
See Menu Nodes → Wizard for complete guide with examples.
📦 Collect List Items (flow.collect)
Build lists dynamically in conversation — collect multiple items one by one, like a shopping list or todo list!
This is perfect when you don't know how many items the user will provide. They keep adding until they say "done"!
Why Use Collect?
Perfect for:
- 🛒 Shopping Lists — "Add milk", "Add bread", "Done"
- ✅ Todo Lists — Collect multiple tasks
- 🎯 Wish Lists — Birthday/holiday items
- 📝 Guest Lists — Event attendees
- 🍕 Order Multiple Items — Pizza toppings, menu items
Unlike a wizard (which asks fixed questions), flow.collect lets users add as many items as they want!
Configuration
| Setting | Description | Example |
|---|---|---|
| Prompt | What to ask for each item | "Add an item to your cart:" |
| DoneKeywords | Words that finish collection | ["done", "finish", "that's all"] |
| DoneButton | Button to finish | "✅ Done Adding" |
| MinItems | Minimum required | 1 |
| MaxItems | Maximum allowed (0 = unlimited) | 10 |
| ShowList | Show collected items after each add? | true |
| ItemTemplate | How to display each item | "{{index}}. {{value}}" |
| SaveToVar | Save result to variable | "var.cart_items" |
Outputs:
- ⚡ Complete — User finished collection
- ⚡ Cancelled — User cancelled
- 📦 Items — Array of collected items
- 🔢 Count — How many items collected
📚 Real-World Example: Shopping Cart
Setup:
{
"prompt": "🛒 What would you like to add to your cart?",
"done_keywords": ["done", "finish", "checkout"],
"done_button": "✅ Proceed to Checkout",
"min_items": 1,
"max_items": 20,
"show_list": true,
"item_template": "{{index}}. {{value}}",
"save_to_var": "var.cart"
}
How it works:
Bot: 🛒 What would you like to add to your cart?
User: Milk
Bot: ✅ Added! Your cart:
1. Milk
Add another item or tap "✅ Proceed to Checkout"
User: Bread
Bot: ✅ Added! Your cart:
1. Milk
2. Bread
User: Eggs
Bot: ✅ Added! Your cart:
1. Milk
2. Bread
3. Eggs
User: done
Bot: → Goes to "Complete" output
Items: ["Milk", "Bread", "Eggs"]
Count: 3
🎯 More Examples
Example 1: Todo List
{
"prompt": "✅ Add a task (or type 'done'):",
"done_keywords": ["done", "finish"],
"min_items": 1,
"show_list": true,
"item_template": "☐ {{value}}",
"save_to_var": "var.todos"
}
Result:
Items: ["Call dentist", "Buy groceries", "Send email"]
Example 2: Party Guest List
{
"prompt": "👥 Add a guest name:",
"done_button": "✅ Finalize Guest List",
"max_items": 50,
"show_list": true,
"save_to_var": "var.guests"
}
Features:
- Shows growing list as names are added
- Stops at 50 guests (max_items)
- Saves to
var.guestsfor invitation system
⚙️ Best Practices
1. Always Show the List
{
"show_list": true // ← Users see what they've added
}
Without this, users forget what they've added!
2. Provide Multiple Ways to Finish
{
"done_keywords": ["done", "finish", "that's all", "checkout"],
"done_button": "✅ Done"
}
3. Set Reasonable Limits
{
"min_items": 1, // At least one item
"max_items": 20 // Don't let them add 1000!
}
4. Use Clear Templates
{
"item_template": "{{index}}. {{value}}"
}
Shows numbered list: "1. Milk", "2. Bread"
💡 Accessing Collected Data
After collection, use the data:
Connect to next node:
Complete output → Send Message:
"Your cart has {{Count}} items: {{join(Items, ', ')}}"
Result:
Your cart has 3 items: Milk, Bread, Eggs
In templates:
{{Items[0]}} // First item
{{Items[1]}} // Second item
{{Count}} // Total count
🔄 Comparison: Collect vs Wizard
| Feature | flow.collect | menu.wizard |
|---|---|---|
| Structure | Unknown # of items | Fixed steps |
| Use Case | Shopping lists, todos | Registration, surveys |
| Flexibility | User decides when done | Pre-defined flow |
| Data Type | Array of items | Object with fields |
Use Collect when: "List as many as you want"
Use Wizard when: "Answer these specific questions"
Subflows (Reusable Pieces)
Call Subflow
Runs a reusable flow and returns
Think of it like calling a helper function:
| Setting | What It Does |
|---|---|
| Subflow | Which subflow to run |
| Inputs | Data to pass in |
Learn more in Subflows →
🔀 Merge Flows (flow.junction)
Combines multiple paths into one.
Use this to avoid duplicating nodes. If you have two different ways to reach the same step (e.g., a command and a button click), merge them here.
| Setting | What It Does |
|---|---|
| Inputs | How many paths to merge (default: 2) |
How it works: It's like a funnel or a highway junction. As soon as ANY input arrives, it immediately continues to the output. It does not wait for all inputs.
Other Control Nodes
Guard
Blocks access based on a condition
Put at the start of protected flows:
| Setting | What It Does |
|---|---|
| Expression | Who can proceed |
| Block Message | What to say if blocked |
Delay
Pauses for a set time
| Setting | What It Does |
|---|---|
| Duration (ms) | How long to wait |
Use Cases:
- Build suspense
- Space out messages
- Rate limiting
Long delays tie up resources. For scheduled actions longer than a few minutes, use the scheduling nodes below! ::
Scheduling
Schedule Task (flow.schedule)
Schedule a flow to run later — at a specific date/time or after a delay.
| Setting | Description |
|---|---|
| TriggerName | Which sys.scheduled_trigger to fire |
| Mode | delay (milliseconds) or datetime (specific time) |
Inputs:
DelayMs— Milliseconds to wait (delay mode)DateTime— ISO datetime string (datetime mode)Params— Data to pass to the scheduled trigger (object)UserID— Target user (default: current user)RepeatCount— How many times (0 = once, N = repeat N times)
Outputs:
NEXT— Scheduled successfullyError— Scheduling failedTaskID— ID for cancellationScheduledFor— When it will run
Example: Reminder System
User: Remind me in 1 hour to call mom
↓
[flow.schedule]
Mode: delay
DelayMs: 3600000 (1 hour)
TriggerName: "reminder_trigger"
Params: { message: "Call mom!", user_id: {{user.id}} }
↓
[Save TaskID to var.reminder_id]
↓
[Send: "✅ Reminder set for 1 hour from now"]
--- Later (after 1 hour) ---
[sys.scheduled_trigger - name: "reminder_trigger"]
↓ Params.message
[Send: "🔔 Reminder: Call mom!"]
[Screenshot: flow.schedule node showing delay mode with 3600000ms delay]
Use Cases:
- Reminders
- Trial expiration
- Follow-up messages
- Appointment notifications
Cancel Scheduled Task (flow.cancel_schedule)
Cancel a previously scheduled task before it runs.
Inputs:
TaskID— The task to cancel
Outputs:
Success— Task cancelledNotFound— Task already ran or doesn't existError— API error
Example: Cancel Reminder
User: Cancel my reminder
↓
[Get var.reminder_id]
↓
[flow.cancel_schedule]
TaskID: {{var.reminder_id}}
↓ Success
[Send: "✅ Reminder cancelled"]
↓ NotFound
[Send: "⚠️ No active reminder found"]
Schedule Message (flow.schedule_message)
Simplified node to schedule a single message — no separate trigger needed!
| Setting | Description |
|---|---|
| Message | Text to send later |
Inputs:
Message— Message text (can use templates)DelayMsorDateTime— When to sendChatID— Target chat (optional)
Outputs:
NEXT— ScheduledTaskID— For cancellation
Example: Welcome Series
User completes registration
↓
[flow.schedule_message]
Message: "👋 Welcome! Here's tip #1: ..."
DelayMs: 86400000 (24 hours)
↓
[flow.schedule_message]
Message: "💡 Tip #2: ..."
DelayMs: 172800000 (48 hours)
[Screenshot: flow.schedule_message node with datetime picker]
Perfect for:
- Drip campaigns
- Welcome sequences
- Quick reminders
- Trial expiration warnings
Counter
Tracks a count that persists
| Mode | What It Does |
|---|---|
| Read | Get current value |
| Increment | Add 1 |
| Reset | Set back to 0 |
Great for:
- Counting uses
- Tracking attempts
- Leaderboards
Common Patterns
Registration Flow
Confirmation Dialog
Retry Logic
Tips
Consider what happens if a user never replies. You might want to set a timeout or reminder.
Long forms have high drop-off rates. Ask only what you need!
Next Steps
- Data Nodes → — Work with the answers you collect
- Subflows → — Create reusable flows
- Forms Guide → — Deep dive on forms