Skip to content

Formula Node ​

TL;DR:

  • What: Prepare data, calculate prices, or format text strings before using them in other nodes.
  • When: You need logic that is more complex than a simple "Set Variable" but does not require an external API call.
  • Result: The bot evaluates your expressions and saves the results into variables. If an error occurs, execution stops at that line.

What it does ​

The Formula node is a background processing block that manipulates data within your scenario. It does not interact with the user or send messages. Instead, it evaluates mathematical or logical expressions line by line and stores the results in variables for later use in Message, Condition, or Actions nodes.

A Formula node does not send messages. To show the result to the user, use a Message node after it and insert the saved variable into the message.

When to use it ​

  • ✅ Calculating totals — Summing up points, calculating discounts, or adding taxes to a price.
  • ✅ Text personalization — Combining a user's name with custom text to create a unique greeting.
  • ✅ Data normalization — Converting user input to lowercase, trimming spaces, or formatting dates.
  • ✅ Logic preparation — Creating a flag variable based on multiple complex checks.

When NOT to use it ​

  • ❌ Sending messages — This node is silent. If you want the user to see the result, you must pass it to a Message node afterwards.
  • ❌ Branching flow — The Formula node always has a single output. To send users down different paths, use a Condition or Random node.
  • ❌ External data — If you need to fetch or send data to external systems, use Actions integrations: Sync HTTP, Webhook, or Email.

In the editor ​

Formula node in the Scenario Editor

Formula uses a code editor in the node settings panel. You write expressions line by line and the bot evaluates them when the user reaches this node.

What you can configure ​

In a Formula node, you write simple expressions that calculate or transform values and save the result into variables.

You can configure:

  • Formula code — One or more assignment lines, such as var.total = var.price * var.quantity.
  • Target variables — Where the calculated result should be saved.
  • Source values — Variables, system fields, UTM values, or global variables used in the calculation.
  • Go to next step — Whether the scenario should continue after the formula runs. Turn this off when the formula should end the flow unless something else resumes it later.
  • Continue on error — When enabled, the flow proceeds to the next node even if the formula fails. This is useful for best-effort calculations where you do not want a single bad expression to stop the entire scenario.

Values you can use ​

A Formula node can read values that already exist in the scenario, such as:

  • Answers saved from Question cards.
  • User profile fields (for example, {%first_name%}, {%email%}, {%phone%}).
  • UTM source values (for example, {%utm_source%}).
  • Workspace global variables (for example, {@my_global@}).
  • Temporary local values used only inside the formula (for example, {*local1*}).

It can save results into scenario variables (var.*), writable fields, UTM values, globals, or local temporary values. Local variables exist only during Formula execution and are not persisted after the node finishes.

System fields (first_name, email, phone, etc.) are read-only — you cannot assign to them. UTM parameters (utm_source, utm_campaign, etc.) and custom fields (F_*) are writable from Formula.

Formula vs Actions ​

Use Actions for simple direct updates:

  • Add a tag.
  • Set a variable to a fixed value.
  • Increment a counter by a fixed number.
  • Clear a field.

Use Formula when the value must be calculated or transformed first:

  • Calculate a total price.
  • Format a date.
  • Normalize text.
  • Combine several values into one.
  • Create a flag based on several conditions.

Formula syntax ​

Each line should assign a result to a target:

text
target = expression

Examples:

text
var.total = var.price * var.quantity
var.name_upper = upper(var.name)
var.has_discount = if(var.coupon == "YES", true, false)

Allowed targets:

  • var.key — scenario session variable (reset on /start)
  • var.key+ — scenario persist variable (survives /start, stored in dedicated Redis key)
  • {*local1*} … {*local5*} — temporary local values
  • {%utm_*%} — UTM parameters
  • {@...@} — workspace globals

Not allowed:

  • System fields ({%first_name%}, etc.)
  • Plain identifiers (for example, x = 1)
  • Function calls as targets

Session vs persist variables

Use var.score for temporary data within one conversation (quiz scores, intermediate totals). Use var.score+ when the value should survive a /start command and be remembered across multiple sessions (loyalty points, completed levels, user preferences).

Output and behavior ​

When a user reaches a Formula node, the bot runs the formula lines from top to bottom.

  • Next step behavior: If the formula runs successfully, the bot follows the Next path when Go to next step is enabled.
  • User-facing result: The user does not see anything from this node directly.
  • Side effects: The node updates the variables or values targeted by your formula.
  • Errors: If one line fails, the Formula node stops at that line and later lines are not processed.

TIP

Use isNull() before doing math with values that may be empty.

Common functions ​

FunctionUse it for
round(x)Round a number
int(x)Convert a value to an integer
float(x)Convert a value to a number with decimals
str(x)Convert a value to text
concat(value, ...)Join values into one text string
len(text)Count text length
upper(text)Convert text to uppercase
lower(text)Convert text to lowercase
if(condition, yes, no)Return one value if true and another if false
isNull(x)Check whether a value is empty
addDays(date, n)Add days to a date
formatDate(date, format)Format a date
Full function reference

Type conversion ​

FunctionReturnsDescription
round(x[, n])numberRound x to n decimal places (default 0 = integer). x must be a number. round(3.14159, 2) → 3.14.
int(x)numberConvert string or number to integer (truncates). x: string | number.
float(x)numberConvert string or number to float. x: string | number.
str(x)stringConvert any value to its string representation.

String ​

FunctionReturnsDescription
concat(value, ...)stringJoin values into one string. Missing variables, null, and empty values become "". Supports escaped newlines such as "\n" and nested functions or macros inside arguments.
len(x)numberLength of a string (or list).
upper(text)stringConvert text to uppercase. text must be a string.
lower(text)stringConvert text to lowercase. text must be a string.
trim(text)stringRemove leading and trailing whitespace from text.
replace(text, search, replacement)stringReplace all occurrences of search in text with replacement. All arguments must be strings.
contains(text, search)booleanReturns True if text contains search (case-sensitive).
substring(text, start, end)stringExtract characters from text from index start (inclusive) to end (exclusive). Both are zero-based integers. substring("hello", 1, 3) → "el".
split(text, sep[, n])stringSplit text by sep, return element at index n (default 0). -1 returns the last element. If the index is out of range, returns "".
match(text, pattern, ignore_case)booleanReturns True if text matches regex pattern. ignore_case is optional boolean (default False). text and pattern must be strings.

Logic ​

FunctionReturnsDescription
if(cond, then, else)anyReturns then value when cond is True, otherwise else. cond must be boolean.
isNull(x)booleanReturns True if x is null or empty.

Date and time ​

All date-arithmetic functions (addYear, addMonth, addDays, addMinutes) accept a date string in ISO-8601 format ("YYYY-MM-DDTHH:MM:SS.sssZ" or "YYYY-MM-DD") and return an ISO-8601 string.

FunctionReturnsDescription
getCurrentTime()stringCurrent UTC date/time as ISO-8601 string. No arguments. Legacy function — prefer now().
now([tz])datetimeCurrent date/time as a typed datetime value. tz is an optional timezone string (for example, "Europe/London", "America/New_York", "Asia/Tokyo", "UTC", "EST"). Defaults to the workspace configured timezone. Use in date arithmetic.
today([tz])dateCurrent date (no time part) as a typed date value in the given timezone. tz is optional. Useful when you need only the date portion.
addYear(date, n)stringAdd n years to date. date: ISO string. n: integer (can be negative).
addMonth(date, n)stringAdd n months to date. date: ISO string. n: integer.
addDays(date, n)stringAdd n days to date. date: ISO string. n: integer.
addMinutes(date, n)stringAdd n minutes to date. date: ISO string. n: integer.
formatDate(date, fmt)stringFormat date using fmt pattern. date: ISO string. fmt: strftime-style format string, for example, "%Y-%m-%d", "%d.%m.%Y", "%H:%M".
dateDiff(d1, d2, unit)numberAbsolute difference between d1 and d2. unit must be exactly "days" or "minutes". Both d1, d2 must be ISO strings. Always returns a positive number.

Random ​

FunctionReturnsDescription
random(min, max)numberRandom integer between min and max (inclusive). Both must be numbers. Limits: min >= -9 999 999, max <= 9 999 999, min <= max.

Identifiers ​

FunctionReturnsDescription
uuid()stringGenerate a UUID string. Useful for one-time refs, attribution keys, and other unique identifiers inside the scenario. No arguments.
Operators and keywords

Arithmetic operators ​

OperatorMeaningOperandsExample
+Add numbers or concatenate stringsnumber + number → number; any other → string2 + 3 → 5;
"Hi, " + var.name → "Hi, John"
-Subtractnumber, number10 - 3 → 7
*Multiplynumber, number4 * 2.5 → 10
/Dividenumber, number9 / 4 → 2.25
%Modulo (remainder)number, number10 % 3 → 1;
8 % 2 → 0

Division or modulo by zero throws a runtime error.

+ is the only mixed-type operator: number + number performs addition, everything else triggers string concatenation (each side is converted with str()).

Comparison operators ​

All comparison operators return a boolean (True or False).

OperatorMeaningAllowed operand types
==Equalsame type only; null == null is allowed
!=Not equalsame type only
>Greater thannumber only
<Less thannumber only
>=Greater or equalnumber only
<=Less or equalnumber only

Comparing null with a non-null value using == or != throws an error. Use isNull() instead.

Boolean operators ​

OperatorMeaningOperands
andLogical ANDboolean, boolean
orLogical ORboolean, boolean
notLogical NOT (unary)boolean

Both operands of and/or must be boolean. not requires a boolean operand.

Operator precedence (high to low) ​

PriorityOperators
1 (highest)not (unary)
2*, /, %
3+, -
4==, !=, >, <, >=, <=
5and
6 (lowest)or

Use parentheses to override: (a + b) * c.

Not supported ​

The following are not available in Formula:

  • Bitwise operators (&, \|, ^, ~, <<, >>)
  • String indexing (text[0])
  • List/array literals ([1, 2, 3])
  • Ternary operator (use if(cond, then, else) instead)
  • Multi-line expressions (each line is a separate statement)
  • ** power operator (use var.x = var.x * var.x or chained *)
Function examples
text
# Type conversion
var.age_int = int(var.age_text)
var.label = str(var.count) + " items"
var.ai_context = concat("Business type: ", var.business_type, "\nOffer: ", var.offer)

# String operations
var.name_upper = upper({%first_name%})
var.preview = substring(var.message, 0, 50)
var.domain = split({%email%}, "@", -1)

# Conditional
var.discount = if({%utm_source%} == "google", "20%", "0%")
var.has_email = not isNull({%email%})

# Date/Time
var.remind_at = addDays(now(), 7)
var.remind_iso = addDays(getCurrentTime(), 7)
var.age_years = int(dateDiff(var.birth_date, getCurrentTime(), "days") / 365)
var.deadline = addMinutes(now("UTC"), 30)

# Random
var.promo_code = random(100000, 999999)
var.lucky_number = random(1, 100)

# Unique identifiers
var.demo_ref = uuid()
{%utm_content%} = uuid()

Examples ​

Example 1: Calculate total score ​

Goal: Compute a final score based on base points and a bonus.
Setup:

text
var.bonus = if(var.user_type == "VIP", 50, 10)
var.final_score = var.base_points + var.bonus

Result: The variable var.final_score is updated and can be displayed in a subsequent Message node using {{var.final_score}}.

Example 2: Normalize user input ​

Goal: Clean up an email address provided by a user.
Setup:

text
var.clean_email = lower(substring(var.raw_email, 0, 100))

Result: Any email stored in var.raw_email is processed, and the cleaned version is saved to var.clean_email.

Example 3: Create a discount based on traffic source ​

Goal: Assign a personalized discount depending on where the user came from.
Setup:

text
var.discount = if({%utm_source%} == "google", 20, 0)

Result: Users from the specified campaign receive a discount value that can be used in a Condition or Message node afterwards.

Example 4: Persist a score across sessions ​

Goal: Save a loyalty score that survives /start and remains available in future sessions.
Setup:

text
var.points+ = var.points+ + 10

Result: The value stored in var.points+ is kept in a separate Redis key per user. When the user sends /start again, the score is still there. Display it with {{var.points}} or {{var.points+}} — both render the same value.

Common pitfalls ​

  • The formula does not save anything → The line may be missing an assignment target. Fix: Use target = expression.

  • The flow stops after Formula → One expression may contain an error (for example, division by zero or a syntax error). Fix: Test each line and check for empty values before calculations. Use isNull() to guard against missing data.

  • A number is treated like text → The value may be stored as a string. Fix: Use int() or float() before doing math.

  • The user cannot see the result → Formula is silent. Fix: Add a Message node after it and display the saved variable.

  • Type mismatch in comparisons → Comparing null with a non-null value using == or != throws an error. Fix: Use isNull() instead.

FAQ ​

  • Q: Can I use variables inside a Formula node?
    A: Yes. You can read saved scenario values and use them in expressions.

  • Q: Does Formula send a message to the user?
    A: No. Formula is silent. Use a Message node after it to show the result.

  • Q: What happens if a value is empty?
    A: Empty values are treated as null. Use isNull() before calculations.

  • Q: Can one Formula node update multiple variables?
    A: Yes. Use one assignment per line. You can have up to 50 lines of code.

  • Q: What is the difference between now() and getCurrentTime()?
    A: getCurrentTime() returns a plain UTC ISO string. now(tz?) returns a typed datetime value that carries timezone information. Use now() for new formulas; getCurrentTime() is kept for backwards compatibility.

  • Q: Do local variables persist after the node?
    A: No. {*local1*} … {*local5*} are temporary and exist only during the current Formula execution.

  • Q: What is the difference between var.score and var.score+?
    A: var.score is a session variable — it is stored in the conversation context and is cleared when the user sends /start. var.score+ is a persist variable — it is stored in a dedicated per-user key in Redis and survives /start. Use var.score for temporary data within one flow and var.score+ for data that should be remembered across multiple sessions. Note: if you write to var.score+, the session copy of var.score is automatically cleared for the same key, and vice versa — only one store holds a given key at a time.

  • Q: What happens if I turn off "Go to next step"?
    A: The flow stops after the Formula node finishes. Use this when the formula should end the flow unless something else resumes it later.

  • Actions Node — Use for simple updates like setting variables, adding tags, or clearing values.
  • Condition Node — Branch the flow based on the result of a formula.
  • Message Node — Show the calculated or formatted result to the user.
  • Schedule Node — Use a calculated date value to continue the flow at a specific time.

TeleGo.io Docs