Scripts
A script calculates and changes variables. The "Script" step runs it.

Under the script field, "Test run" shows what would change. It saves nothing.
Syntax
Every line assigns a value: variable = expression. A line that starts with # is a comment.
# Without a prefix a variable lives while the conversation lasts
options = filter(app.slots, item.status == "free")
labels = map(options, item.time)
# user.* stays with the visitor for good
user.score = default(user.score, 0) + 10
user.level = user.score > 100 ? 2 : 1
# app.* is the bot's shared data
app.total_plays = default(app.total_plays, 0) + 1
- Values can be numbers, strings in quotes
"…",true,false,null, lists[a, b]and objects{key: value}. - A string in backticks fills values in:
`${item.name} ×${item.qty}`givesShoes ×2. Any expression goes inside${…}. An empty value fills in as nothing. - An expression may go on over several lines while a bracket is open.
- A comment takes a whole line. A comment at the end of a line of code does not work.
- A list element is
items[0]oritems.0. The size of a list or an object islist.size. - A variable nothing was assigned to yet is
null.default(x, fallback)gives a fallback value. -
==compares strictly:1 == "1"is false.
Namespaces
The prefix before a variable's name says where it lives and for how long.
| Variable | What it is |
|---|---|
name or session.name
|
Conversation variables. A name without a prefix writes and reads here. They go away when the conversation ends. |
user.name |
One visitor's state, for good. Read and write. Steps save answers here: input, choices, polls. |
app.name |
Data shared by all visitors. Read and write. Counters, a catalog, a schedule. |
temp.name |
Variables of one script. They live while it runs and are saved nowhere. |
platform.* |
Telegram's data about the person and the message being handled. Read only. See below. |
initiator.* |
Who started this flow through "Notify" or "Announce": user_id, name, username. Read only. Empty in an ordinary flow. |
secret.* |
The bot's secrets from "Bot settings → Secrets": API keys, passwords. Read-only, and only inside a request: fetch, page_meta, save_image. |
- A field inside an object cannot be changed directly:
user.cart.qty = 1does not work. Build the object again, or change a list element withupdate_where. - Two chats can change
app.*at the same time. Neither change gets lost. - Names starting with
_are reserved. - A rule of thumb: keep what the person entered in
user.*, and what a script calculated in conversation variables.
Operators
| What | Operators |
|---|---|
| Arithmetic | + - * / % |
| Comparison | > < >= <= == != |
| Logic | && || ! |
| A condition in one expression | user.x > 5 ? "high" : "low" |
| Joining strings | "Hello, " + platform.first_name |
+ joins when either side is a string. Otherwise both sides must be numbers.
Functions
Math
| Function | What it does |
|---|---|
min(a, b, ...) max(a, b, ...)
|
Minimum and maximum |
abs(x) |
Absolute value |
floor(x) ceil(x) round(x, decimals?)
|
Rounding down, up and to the nearest, to the given number of decimals |
sqrt(x) pow(x, y)
|
Square root and power |
clamp(val, min, max) |
Keep a number within a range |
Strings
| Function | What it does |
|---|---|
len(s) |
Length of a string or a list |
upper(s) lower(s) trim(s)
|
Upper case, lower case, no spaces at the ends |
contains(s, sub) |
Whether a string contains a substring |
starts_with(s, prefix) ends_with(s, suffix)
|
Whether a string starts or ends with this text |
substr(s, start, len?) |
Cut out a substring |
replace(s, from, to) |
Replace every occurrence |
split(s, sep) |
Split a string into a list by a separator |
Lists
| Function | What it does |
|---|---|
count(list) |
Number of elements |
sort(list) unique(list)
|
The list sorted, and the list without repeats |
join(list, ", ") |
List → string |
append(list, item) |
Add an element to the end of a list |
sort_by(list, "key", "desc") |
Sort by a field; "asc" ascending, "desc" descending |
take(list, n) |
The first n elements |
slice(list, start, count) |
A part of a list from position start (counting from 0) |
last(list, n) |
The last n elements, in the same order |
sum(list) |
Sum of numbers |
nth(list, i) |
An element by its position; outside the list, null
|
Lists with item
In these functions the second argument is an expression in which item is the current element.
| Function | What it does |
|---|---|
filter(list, item.status == "free") |
Keep the elements the expression holds for |
map(list, item.time) |
Collect a value from every element |
flat_map(list, item.slots) |
Like map, but the lists from every element merge into one |
find(list, item.id == 5) |
The first matching element, or null
|
update_where(list, item.id == 5, "status", "busy") |
Change a field in the matching elements |
These calls cannot be nested in one another's condition.
Dates
Dates are ISO strings that start with YYYY-MM-DD.
| Function | What it does |
|---|---|
add_days(date, n) |
Move a date by n days; the result looks like "yyyy-MM-dd"
|
get_iso_day(date) |
Day of the week: 1 is Monday … 7 is Sunday |
format(date, "dd.MM") |
Formatting with date-fns tokens: dd, MM, yyyy, HH:mm
|
Conversion and checks
| Function | What it does |
|---|---|
num(x) str(x) bool(x)
|
To a number, a string or a boolean; num fails when x is not a number |
to_num(x, fallback) |
A number, or fallback when x is not a number |
is_num(x) is_str(x) is_bool(x) is_null(x)
|
Return true or false; is_null is also true for a variable that does not exist yet |
default(x, fallback) |
fallback when x is null; 0 and "" stay as they are |
Calling other services
fetch calls another service over the internet.
temp.resp = fetch("https://api.example.com/items", {method: "POST", body: {q: 1}, cache: 60, timeout: 10})
user.saved = temp.resp.status == 200 ? temp.resp.body.id : null
-
fetchtakes the whole right-hand side of a line. Its result must go totemp.x(gone after the script) orsession.x(kept for the conversation). -
method,headersandbodywork as in any HTTP request. An object inbodyis sent as JSON, a string as it is. -
timeoutis how many seconds to wait for an answer: 5 unless set, 30 at most. -
cacheis how many seconds to keep a successful answer, an hour at most. The cache is shared by everyone who makes the same request. It suits a schedule or a catalog, never a booking. - The result is
{status, body, error}.bodyis parsed JSON or text. When the request did not get through,statusisnullanderrorsays why. - A failed request is not a script error and does not lead to the "+ Error step" branch. Check
temp.resp.status == 200and branch with a "Condition" step. - While a request runs, the person sees "typing…". Neighbouring requests that do not depend on each other run in parallel, up to 6 per script.
Secrets
Do not write an API key into a script in plain text: save it as a secret and read it in a request as secret.API_KEY. More on the Secrets page.
Page data and pictures
page_meta and save_image follow the same rules as fetch.
temp.page = page_meta(link)
temp.photo = save_image(temp.page.image)
app.catalog = append(app.catalog, {id: "p1", name: temp.page.title, image: temp.photo.url})
-
page_meta(url)reads what a page says about itself:{status, title, description, image, images, url, site, type, extra, error}.imagesholds up to 10 of the page's pictures, the main one first. -
save_image(url)copies a picture into the bot's media:{url, urls, error}. It also takes a list, up to 10 pictures, each up to 20 MB. A test run does not save the picture.
Platform variables
Read only.
| Variable | What it is |
|---|---|
platform.user_id |
The person's Telegram ID (a number) |
platform.first_name |
First name |
platform.last_name |
Last name |
platform.full_name |
First and last name; either may be missing |
platform.username |
@username |
platform.language_code |
Language code (uk, en, …) |
platform.is_premium |
Whether the person has Telegram Premium (true / false) |
platform.first_seen |
When the person first wrote to the bot (ISO 8601) |
platform.last_seen |
When the person last did anything in the bot (ISO 8601) |
platform.message_text |
Text of the message being handled |
platform.start_param |
The data after the dash in a link to a flow; present only in reply to /start, and the conversation it opened keeps it as session.start_param
|
platform.now |
The current moment, ISO 8601 |
Limits
| What | How much |
|---|---|
| Expression length | 1024 characters |
Statements (a fetch over several lines counts as one) |
50 |
| Script length | 4096 characters |
| List length | 500 |
Elements in one {…} or […]
|
50 |
fetch wait |
5 s unless set; 30 s at most |
fetch answer |
1 MiB |
fetch cache |
1 hour |