Common Errors
The mistakes that trip people up most often, with the fix for each.
Template: mustache instead of ${}β
Botgami uses backtick templates with ${}. Mustache {{ }} does nothing.
// WRONG
send.text("Hello {{name}}")
// RIGHT
send.text(`Hello ${user.first_name}`)
Also note the literal "..." string above isn't a template at all β only backtick strings interpolate.
Wrapping expressions in expr(...)β
Expressions are bare. There's no expr() wrapper.
// WRONG
when expr("user.age > 18") { /* β¦ */ }
// RIGHT
when user.age > 18 { /* β¦ */ }
Menu button with no handler subflowβ
Every text "Label" -> handler() button in a menu declaration needs a matching subflow. Without it, tapping the button does nothing and the editor flags it.
// WRONG β buyItem subflow not declared:
menu shopMenu {
text "Buy" -> buyItem()
}
// RIGHT β add the handler:
menu shopMenu {
text "Buy" -> buyItem()
}
subflow buyItem() {
send.text("Buyingβ¦")
}
Raw callback_data button with no handler (escape hatch)β
When using the raw inline_keyboard form (e.g. from a webhook-triggered message), every callback_data needs a matching flow ... on callback:
// You sent this button:
send.text("Menu", reply_markup: { inline_keyboard: [[{ text: "Buy", callback_data: "buy" }]] })
// β¦so you MUST have this:
flow buy on callback "buy" {
send.text("Buyingβ¦")
}
Prefer a named menu declaration over raw inline_keyboard for anything you author by hand. Menus catch missing handlers at edit time, not at runtime.
HTTP without on errorβ
A bare HTTP call that fails aborts the flow silently.
// WRONG
res = http.get(url: flow.url)
// RIGHT
res = http.get(url: flow.url) on error {
send.text("Service unavailable, try again.")
stop
}
Wrong types namespaceβ
Type conversion and checks live in types.*, not type.*.
// WRONG
set { flow.s = type.string(flow.n) }
// RIGHT
set { flow.s = types.string(flow.n) }
Assigning a variable without set { }β
You write persistent variables inside a set { } block, not with a function call.
// WRONG
set("visits", 1)
// RIGHT
set { user.visits = 1 }
Missing scope prefixβ
Persistent variables always carry a scope prefix. A bare name is a flow-local.
// WRONG β `balance` is just a throwaway local here
set { balance = balance + 10 }
// RIGHT
set { user.balance = user.balance + 10 }
Arithmetic inside ${}β
${} takes simple references only. Compute first.
// WRONG
send.text(`Total: ${flow.price * flow.qty}`)
// RIGHT
set { flow.total = flow.price * flow.qty }
send.text(`Total: ${flow.total}`)
Appending a single element with <<β
<< appends an array. Wrap a single element.
// WRONG
set { user.todos << { title: "Buy milk" } }
// RIGHT
set { user.todos << [{ title: "Buy milk" }] }
Hardcoding secretsβ
Secrets go in global.*, set in the Variables panel.
// WRONG
res = http.get(url: "https://api.example.com", bearer: "sk_live_abc123")
// RIGHT
global api_key: String = "" // set value in Variables panel
res = http.get(url: "https://api.example.com", bearer: global.api_key)
Inline webhook secretβ
The webhook secret must be a global.*/shared.* reference β plaintext is rejected.
// WRONG
flow ipn on webhook("x", verify: token, secret: "mytoken") { /* β¦ */ }
// RIGHT
global webhook_token: String = ""
flow ipn on webhook("x", verify: token, secret: global.webhook_token) { /* β¦ */ }
Quick referenceβ
| Wrong | Right |
|---|---|
{{name}} | `${user.first_name}` (backtick template) |
expr("user.age > 18") | user.age > 18 |
type.string(x) | types.string(x) |
set("k", v) | set { user.k = v } |
Bare balance | user.balance |
${a * b} | precompute in set { }, then ${result} |
arr << x | arr << [x] |
menu button with no subflow | Add the matching subflow handler() { } |
Raw button with no on callback | Add the matching callback flow |
http.get(...) with no on error | Add on error { β¦ } |
| Inline secret | global.* reference |