Skip to main content

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 { /* … */ }

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…")
}
tip

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​

WrongRight
{{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 balanceuser.balance
${a * b}precompute in set { }, then ${result}
arr << xarr << [x]
menu button with no subflowAdd the matching subflow handler() { }
Raw button with no on callbackAdd the matching callback flow
http.get(...) with no on errorAdd on error { … }
Inline secretglobal.* reference

Next​