Subflows
A subflow is a reusable, parameterized block of logic — Botgami's equivalent of a function. Extract a subflow when the same steps appear in more than one flow, or when a flow gets long enough to name its parts.
Declaring a subflow
Declare subflows inside the bot block:
subflow greet(name: String) {
send.text(`👋 Welcome, ${name}!`)
return {}
}
greet— the name.(name: String)— typed parameters.return {}— every subflow ends by returning an object (empty if it has nothing to hand back).
Calling with await
Call a subflow with await, passing arguments by name:
flow start on command "/start" {
await greet(name: user.first_name)
}
The caller waits for the subflow to finish before continuing.
Returning values
Declare outputs in the return { } object, then destructure them at the call site with let { … } = await …:
subflow make_order(plan: String) -> { order_id: String, price: Number } {
set { flow.price = plan == "yearly" ? 89 : 9 }
return { order_id: id.uuid(), price: flow.price }
}
flow buy on command "/buy" {
let { order_id, price } = await make_order(plan: "yearly")
send.text(`Order ${order_id} — $${price}`)
}
Destructuring binds each name to the matching output key. Renaming during destructuring (let { x: alias }) is not supported — use the output's own name.
fail { } for error outputs
A subflow can signal failure with fail { } instead of return { }. Catch it at the call site with an on error { } handler:
subflow charge(amount: Number) -> { receipt: String } {
when amount <= 0 {
fail { reason: "Amount must be positive" }
}
return { receipt: `Charged $${amount}` }
}
flow pay on command "/pay" {
let { receipt } = await charge(amount: 0) on error {
send.text("❌ Payment failed — please try again.")
stop
}
send.text(receipt)
}
See Error handling for the full on error pattern.
ask.* works inside subflows
You can collect input inside a subflow — the wait-and-resume works exactly as in a top-level flow. This is how the trivia blueprint factors a single graded question into a reusable ask_question subflow:
subflow ask_question(q: { question: String, answer: String, points: Number }) {
branch ask.text(q.question, timeout: "60s") {
"timeout" { send.text("⏱ Time's up!") }
default {
when str.lower(str.trim(answer)) == str.lower(str.trim(q.answer)) {
set { user.score += q.points }
send.text(`✅ Correct! +${q.points}`)
} else {
send.text(`❌ The answer was ${q.answer}.`)
}
}
}
return {}
}
flow play on command "/play" {
foreach q in flow.round {
await ask_question(q)
}
}
Subflows can mutate state
A subflow shares the caller's user.*, shared.*, and global.* scopes. Writes inside it persist back to the caller — that's why ask_question above can update user.score directly.
When to extract a subflow
- The same sequence appears in two or more flows (e.g. a command and its callback variant).
- A flow has grown long and a named step would clarify it.
- You need a routine that both a command and a webhook/schedule can call.
Next
- Error handling
- Standard Library — bundled subflow collections