Skip to main content

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

ScopeLifetimeWho can write itUse for
user.*Persistent, per userThe current user's flowPer-user state (balance, settings, history)
shared.*Persistent, bot-wideAny flowCross-user data (leaderboards, ledgers, config you change at runtime)
global.*Set once, read-only at runtimeSet in the Variables panelSecrets and static config (API keys, admin id, prices)
flow.*Ephemeral, one flow runThe current flowScratch 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
}
warning

Never store long-lived state in flow.* — it vanishes the moment the flow ends. Use user.* or shared.* for anything that must survive.

danger

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:

ReferenceWhat it is
user.idThe current user's Telegram id
user.first_name, user.last_name, user.usernameProfile fields
user.language_codeTheir language
user.chat.id, user.chat.type, user.chat.titleThe current chat
bot.id, bot.usernameYour bot
bot.webhook_baseBase URL for your webhook triggers
callback.data, callback.message, callback.from, callback.idIn a callback flow
command.*In a command flow
message.text, message.message_id, message.reply_to_messageIn 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

TypeExample values
String"hello"
Number42, 3.14
Booleantrue, false
Anyanything
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

OpEffectExample
=Assignuser.name = "Sam"
+=Add to a numberuser.balance += 10
-=Subtract from a numberuser.balance -= 5
<< (append)Append items to an arrayuser.todos << [{ ... }]
prependPrepend to an arrayset { user.queue prepend [x] }
incrementAdd a numberset { user.score increment 1 }
decrementSubtract a numberset { user.score decrement 1 }
mergeMerge object fieldsset { user.profile merge { city: "NYC" } }
deleteRemove a keyset { user.profile delete "city" }
toggleFlip a booleanset { user.muted toggle true }
ensureSet only if currently nil/missingset { user.created ensure time.timestamp() }

The common ones — =, +=, -=, and << — read like operators. The rest are named operations.

note

<< 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}`)

Next