Variables and State
State is how your bot remembers things — a user's balance, a shared leaderboard, your API key. Botgami has four variable scopes plus a set of read-only context values.
The four scopes
| Scope | Lifetime | Who can write it | Use for |
|---|---|---|---|
user.* | Persistent, per user | The current user's flow | Per-user state (balance, settings, history) |
shared.* | Persistent, bot-wide | Any flow | Cross-user data (leaderboards, ledgers, config you change at runtime) |
global.* | Set once, read-only at runtime | Set in the Variables panel | Secrets and static config (API keys, admin id, prices) |
flow.* | Ephemeral, one flow run | The current flow | Scratch values within a single execution |
bot Bank {
user balance: Number = 0 // per user
shared total_deposits: Number = 0 // across everyone
global daily_limit: Number = 1000 // config, read-only
}
Never store long-lived state in flow.* — it vanishes the moment the flow ends. Use user.* or shared.* for anything that must survive.
Put all secrets and API keys in global.* and set their values in the Variables panel. Never hardcode a secret in your .botgami source.
Read-only context
Telegram and the runtime inject read-only values you can read but not assign. The most common:
| Reference | What it is |
|---|---|
user.id | The current user's Telegram id |
user.first_name, user.last_name, user.username | Profile fields |
user.language_code | Their language |
user.chat.id, user.chat.type, user.chat.title | The current chat |
bot.id, bot.username | Your bot |
bot.webhook_base | Base URL for your webhook triggers |
callback.data, callback.message, callback.from, callback.id | In a callback flow |
command.* | In a command flow |
message.text, message.message_id, message.reply_to_message | In a message flow |
request.* | In a webhook flow (see Webhooks) |
member.*, request.from.* | In group-event flows |
send.text(`Hi ${user.first_name}! Your id is ${user.id}.`)
Declaring typed variables
Declare user, shared, and global variables at the top of the bot block with a scope keyword, a name, a type, and an optional default:
user name: String = ""
user age: Number = 0
user verified: Boolean = false
user notes: Any = null
// Collections
user tags: Array<String> = []
shared scores: Map<String, Number> = {}
// Object-shaped arrays (very common for ledgers/catalogs)
shared products: Array<{ id: String, name: String, price: Number }> = []
// Enums and unions
user role: enum("customer", "admin") = "customer"
user plan: String = "none" // "none" | "monthly" | "yearly"
flow.* variables don't need declaration — they're inferred from your first write.
Types at a glance
| Type | Example values |
|---|---|
String | "hello" |
Number | 42, 3.14 |
Boolean | true, false |
Any | anything |
Array<T> | [1, 2, 3] |
Map<K, V> | { "a": 1 } |
{ field: Type, … } | { id: "x", n: 1 } |
enum("a", "b") | "a" or "b" |
Writing with set { }
To assign or update a variable, use a set { } block. Each line is target op value:
set {
user.name = flow.trimmed
user.balance += 10
user.todos << [{ title: "Buy milk", done: false }]
}
The 9 set operations
| Op | Effect | Example |
|---|---|---|
= | Assign | user.name = "Sam" |
+= | Add to a number | user.balance += 10 |
-= | Subtract from a number | user.balance -= 5 |
<< (append) | Append items to an array | user.todos << [{ ... }] |
prepend | Prepend to an array | set { user.queue prepend [x] } |
increment | Add a number | set { user.score increment 1 } |
decrement | Subtract a number | set { user.score decrement 1 } |
merge | Merge object fields | set { user.profile merge { city: "NYC" } } |
delete | Remove a key | set { user.profile delete "city" } |
toggle | Flip a boolean | set { user.muted toggle true } |
ensure | Set only if currently nil/missing | set { user.created ensure time.timestamp() } |
The common ones — =, +=, -=, and << — read like operators. The rest are named operations.
<< appends an array of items. To add one element, wrap it: user.todos << [item].
Reading variables
Just reference them — in expressions, conditions, and templates:
when user.balance >= global.daily_limit {
send.text("You've hit today's limit.")
}
send.text(`Balance: ${user.balance}`)