Your First Flow
A flow is a named block of logic that runs when a trigger fires. Let's build one up step by step, then add a menu, a button handler, and a variable.
Step 1: Respond to /start
bot FirstFlow {
flow start on command "/start" {
send.text("Hello! 👋")
}
}
flow start on command "/start" says: when a user sends /start, run this body. The body sends one message.
Step 2: Declare a menu
Menus are declared once at the top of the bot block and attached to any message with reply_markup. Each button calls a subflow handler:
bot FirstFlow {
menu startMenu {
text "🎉 Surprise me" -> doSurprise()
}
flow start on command "/start" {
send.text("Hello! 👋", reply_markup: startMenu)
}
}
The menu lives next to your bot definition. You can attach it to any send.* or edit.* call.
Step 3: Handle the button press
Add the handler subflow. The menu routes each press here automatically:
subflow doSurprise() {
send.text("🎉 Surprise! Thanks for tapping.")
}
The full bot so far:
bot FirstFlow {
menu startMenu {
text "🎉 Surprise me" -> doSurprise()
}
flow start on command "/start" {
send.text("Hello! 👋", reply_markup: startMenu)
}
subflow doSurprise() {
send.text("🎉 Surprise! Thanks for tapping.")
}
}
Step 4: Add a variable
Variables hold state. A user.* variable persists per user across sessions. Declare it at the top of the bot block, then write to it inside a set { ... } block.
bot FirstFlow {
user taps: Number = 0
menu startMenu {
text "🎉 Surprise me" -> doSurprise()
}
flow start on command "/start" {
send.text("Hello! 👋", reply_markup: startMenu)
}
subflow doSurprise() {
set { user.taps += 1 }
send.text(`🎉 You've tapped me ${user.taps} time(s)!`)
}
}
Note the two ways values appear:
set { user.taps += 1 }— write a variable (here, increment by 1).`... ${user.taps} ...`— read a variable inside a backtick template.
Step 5: Show current state in the menu
Because menus re-render from current state on every press, you can show a live counter directly in a button label:
menu startMenu {
text `🎉 Tap (${user.taps})` -> doSurprise()
}
Every press updates user.taps, then re-renders the menu — the label stays in sync automatically.
Step 6: Redeploy
Click Publish again. Every time you change your .botgami and publish, the live bot picks up the new logic. Existing user.* values are preserved.
Recap
| Piece | What it does |
|---|---|
flow name on command "/start" | Runs on a slash command |
menu name { text "Label" -> handler() } | Declares a named keyboard |
send.text(..., reply_markup: menuName) | Attaches the menu to a message |
subflow name() { ... } | Handles a button press |
user x: Number = 0 | Declares persistent per-user state |
set { user.x += 1 } | Writes a variable |
`${user.x}` | Reads a variable into text |