Skip to main content

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.

SettingWhat It Does
PromptThe question/message to send
ExpectType of answer (text, number, photo, etc.)
Save to VariableWhere to store the valid answer
ValidationOptional expression (e.g. val > 10)
Retry MessageWhat to say if validation fails

Supported Input Types:

  • text - Any text
  • number - Validates it's a number
  • photo/video/audio - Waits for media
  • choice - Matches against a list
  • regex - Matches a pattern
  • callback - 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
SettingWhat It Does
ArrayThe 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
SettingWhat It Does
ArrayThe list
DelayMilliseconds 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!

NEW Power Node!

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

SettingDescriptionExample
PromptWhat to ask for each item"Add an item to your cart:"
DoneKeywordsWords that finish collection["done", "finish", "that's all"]
DoneButtonButton to finish"✅ Done Adding"
MinItemsMinimum required1
MaxItemsMaximum allowed (0 = unlimited)10
ShowListShow collected items after each add?true
ItemTemplateHow to display each item"{{index}}. {{value}}"
SaveToVarSave 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.guests for 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

Featureflow.collectmenu.wizard
StructureUnknown # of itemsFixed steps
Use CaseShopping lists, todosRegistration, surveys
FlexibilityUser decides when donePre-defined flow
Data TypeArray of itemsObject 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:

SettingWhat It Does
SubflowWhich subflow to run
InputsData 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.

SettingWhat It Does
InputsHow 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:

SettingWhat It Does
ExpressionWho can proceed
Block MessageWhat to say if blocked

Delay

Pauses for a set time

SettingWhat It Does
Duration (ms)How long to wait

Use Cases:

  • Build suspense
  • Space out messages
  • Rate limiting
Keep Delays Short

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.

SettingDescription
TriggerNameWhich sys.scheduled_trigger to fire
Modedelay (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 successfully
  • Error — Scheduling failed
  • TaskID — ID for cancellation
  • ScheduledFor — 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 cancelled
  • NotFound — Task already ran or doesn't exist
  • Error — 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!

SettingDescription
MessageText to send later

Inputs:

  • Message — Message text (can use templates)
  • DelayMs or DateTime — When to send
  • ChatID — Target chat (optional)

Outputs:

  • NEXT — Scheduled
  • TaskID — 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

ModeWhat It Does
ReadGet current value
IncrementAdd 1
ResetSet back to 0

Great for:

  • Counting uses
  • Tracking attempts
  • Leaderboards

Common Patterns

Registration Flow

Confirmation Dialog

Retry Logic


Tips

Timeouts

Consider what happens if a user never replies. You might want to set a timeout or reminder.

Keep Forms Short

Long forms have high drop-off rates. Ask only what you need!


Next Steps