Execution Flow¶
TBL matched a command. Now what? Execution follows a fixed pipeline every time — same order, same rules. Understanding it saves you from duplicate messages, mystery double-replies, and "why didn't my logic run?" moments.
Full pipeline¶
┌─────────────────────────────────────┐
│ 1. Update received │
└─────────────────┬───────────────────┘
▼
┌─────────────────────────────────────┐
│ 2. `@` initialization (always) │
│ Skips answer/keyboard side │
│ effects — logic only │
└─────────────────┬───────────────────┘
▼
┌─────────────────────────────────────┐
│ 3. Matched command │
│ a. Group-only check │
│ b. Need Reply? → send Answer, │
│ start session, STOP │
│ c. Send Answer + Keyboard │
│ d. Run Logic │
└─────────────────┬───────────────────┘
▼
┌────────┴────────┐
│ Runtime error? │
└────────┬────────┘
yes │ no
▼
┌─────────────────────────────────────┐
│ 4. `!` error handler (on error) │
│ Sends its Answer, runs Logic │
└─────────────────┬───────────────────┘
▼
┌─────────────────────────────────────┐
│ 5. `@@` post-processor (always) │
│ Skips answer side effects │
└─────────────────┬───────────────────┘
▼
Complete
Special commands (@, !, @@) are covered in Special Commands. Matching happens before step 3 — see Matching & Priority.
Answer before Logic¶
For normal Telegram commands, the Answer (and Keyboard) goes out first, then Logic runs.
// Answer field: "Loading..."
// Logic:
await HTTP.get("https://api.example.com/data")
Bot.sendMessage(chat.id, "Done!")
The user sees "Loading..." immediately, then "Done!" when logic finishes. No waiting for your API call to show something.
Tip
Put static text in Answer. Put dynamic work in Logic. Bot is for messages Logic sends on its own.
Special command behavior¶
These run automatically — you don't trigger them manually:
| Command | When | Answer sent? | Logic runs? |
|---|---|---|---|
@ | Before every command | No | Yes |
| Matched command | Per update | Yes (unless Need Reply blocks logic) | Yes |
! | On runtime error | Yes | Yes |
@@ | After every command | No | Yes |
* | No other match | Yes | Yes |
@ and @@ skip the automatic Answer/keyboard send — setup and cleanup logic only. No accidental double messages.
Need Reply flow¶
Need Reply breaks the normal "Answer then Logic" rhythm on the first visit:
- Answer (+ keyboard) is sent
- A session is stored for this user
- Logic does not run yet
- User's next message triggers the same command's Logic
- That message is available as
message/params
Send a different valid command (e.g. /start) and the session cancels — the new command runs normally.
See Handling User Input.
Callback query flow¶
Inline button taps send a callback query. TBL treats callback_query.data as command input — same matching rules as message text.
- Match command from
callback_data(command name + optional params) - Send Answer if configured
- Run Logic
Always call Api.answerCallbackQuery() in Logic to dismiss Telegram's loading spinner. Users hate spinners that never stop.
See Handling Callbacks. Callback globals: update, request.
Webhook & webapp flow¶
HTTP-triggered commands skip the Answer field. Only Logic runs, using res for output.
Public web commands skip the sandbox entirely — command source is served as static content. No Logic, no Bot, no database.
| Surface | Answer | Logic | res |
|---|---|---|---|
| Telegram / callback | Yes | Yes | No |
| Webhook / webapp | No | Yes | Yes |
| Public web | No | No | No |
Error handling¶
If Logic throws an error:
- Platform may send a default error message to the user
!command runs (if defined) — its Answer and Logic execute@@still runs afterward
Use ! to log errors or send a friendly fallback:
Chained commands¶
Bot.run("otherCommand") triggers another command inside the same execution. Chain depth is limited (max 6 for Telegram commands).
Webhooks have a separate depth limit. Don't go infinite — Telegram users have patience limits too.
See also¶
- Matching & Priority — how we get to step 3
- Special Commands —
@,!,@@,* - Command Fields — what Answer and Logic actually are