Skip to main content

Control Flow

Control flow decides which steps run. Botgami gives you conditionals, multi-way branching, pattern matching, loops, and error handling.

when / else​

The everyday conditional:

when user.balance >= 100 {
send.text("You can afford it!")
} else {
send.text("Not enough balance.")
}

The else is optional. You can nest when blocks freely.

when … if guard​

Add an extra guard condition with if:

when user.role == "admin" if user.verified {
send.text("Verified admin access granted.")
}

The body runs only when both the main condition and the if guard are true.

branch​

branch routes on a string value into named arms, with optional default, timeout, and error arms. It's the standard way to handle an ask.* result:

branch ask.text("Confirm? (yes/no)", timeout: "60s") {
"yes" {
send.text("Confirmed!")
}
"no" {
send.text("Cancelled.")
stop
}
"timeout" {
send.text("Timed out — no response.")
}
"error" {
send.text("Something went wrong.")
}
default {
send.text("Please reply yes or no.")
}
}
  • String arms ("yes", "no") match exact values.
  • default runs when nothing else matched.
  • timeout / error handle the failure paths of a blocking step like ask.*.

match​

match is richer pattern routing — literal values, numeric ranges, and a wildcard _:

match user.score {
0 {
send.text("No points yet.")
}
1..50 {
send.text("Getting started.")
}
51..100 {
send.text("Pro player!")
}
_ {
send.text("Off the charts! 🚀")
}
}
  • A literal (0, "free", true) matches that exact value.
  • A range (1..50) matches numbers in that inclusive span. Where ranges overlap, the narrowest wins.
  • _ is the catch-all wildcard. Without a _ arm, a subject that matches nothing simply continues after the match.
  • Arms must be distinct — two arms with the same pattern is an error.

Conditions inside an arm​

match has no per-arm if guard. Put the condition in the arm body:

match user.tier {
"pro" {
when user.score > 50 {
send.text("Pro player!")
} else {
send.text("Pro, but still warming up.")
}
}
_ {
send.text("Free tier.")
}
}

An arm is chosen by its pattern alone, so a failing condition cannot fall through to the next arm — handle both outcomes inside the arm you landed in.

foreach​

Iterate over an array. Optionally capture the index as a second name:

foreach item in user.cart {
send.text(`• ${item.name}`)
}

// With index (0-based)
foreach item, i in flow.ranked {
set { flow.pos = i + 1 }
send.text(`${flow.pos}. ${item.name}`)
}
note

There is no while loop in Botgami. Use foreach over an array. To iterate a fixed number of times, build a range with array.range(start, end) and foreach over it.

try / catch / finally​

Catch runtime errors from a block:

try {
lk = chat.invite_link(chat_id: shared.group_chat_id, name: "Invite")
send.text(`Here's your link: ${lk}`)
} catch err {
send.text(`Couldn't create a link: ${err}`)
} finally {
set { shared.attempts += 1 }
}

catch binds the error to a name (err); finally always runs. For HTTP and await calls, prefer the inline on error { } handler — see Error handling.

stop​

End the current flow immediately:

when user.banned {
send.text("🚫 You are banned.")
stop
}
send.text("Welcome!")

Next​