Skip to main content

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}`)
}
warning

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