Skip to main content

Expressions

Expression is an expression language which consists of the functions provided by Excel with a few additions.

Like with functions, expressions can be used directly in cards.

Expressions syntax

Expressions can be referenced directly in the code, but need to be prefixed with a @ symbol when used inside strings. When used inside strings, it is also possible to define an expression block @(), inside which expressions can be used without the @ prefix.

Function and variable names are not case-sensitive, so UPPER is equivalent to upper.

The following example shows three different ways of using expressions to concatenate strings:

card First, then: Second do
a = "pizza"
b = "cheese"

# Reference expressions directly
result = concatenate(a, " with ", b)

# Sends "Tonight we have pizza with cheese"
text("Tonight we have @result")
end

card Second, then: Third do
a = "pizza"
b = "cheese"

# Reference expressions in a @() block inside a string
# Sends "Tonight we have pizza with cheese"
text("Tonight we have @(concatenate(a, \" with \", b))")
end

card Third do
a = "pizza"
b = "cheese"

# Reference expressions inside a string
# Sends "Tonight we have pizza with cheese"
text("Tonight we have @a with @b")
end

Expression v3

Expression v3 is the newest version of the expression engine. Journeys are being moved over gradually: existing journeys keep evaluating exactly as before, and journeys that use v3 say so in the journey settings panel and in the code block editor.

What changes in v3

In earlier versions, values stored as text — contact fields are a common example — were automatically converted to typed values before your expressions saw them: "30" became the number 30, "true" became a boolean, and text that looked like a date became a date. This was convenient but unpredictable: a postal code like "04102" could silently turn into a number and lose its leading zero.

In v3, values keep their original types. Text stays text unless you convert it yourself.

Key casing is also preserved: earlier versions lowercased all field names (a field stored as FirstName became firstname), while v3 keeps them exactly as stored. This doesn't change how you reference values — lookups are case-insensitive either way, so @contact.firstname still finds a field stored as FirstName — but when you display or loop over a whole map, its keys appear with their original casing.

Most journeys are unaffected:

  • Numeric comparisons still work with text that contains a number — @(age > 21) is true when age is "30", because comparisons parse numbers when needed.
  • Rendering values into messages ("Hello @contact.name") is unchanged.

The place to pay attention is anywhere a value is used as a type other than text:

card Check when contact.opted_in == "true" do
# In v3, compare text fields against text explicitly.
# (In v2, "true" was silently converted to a boolean.)
text("You are opted in!")
end

Converting values explicitly

v3 adds conversion functions so you can be deliberate about types:

card MyCard do
age = int(contact.age) # "30" -> 30
score = number(contact.score) # "4.5" -> 4.5
opted = boolean(contact.opted) # "true" -> true
code = text(contact.postcode) # anything -> text

text("Next year you will be @(age + 1)")
end

If a value cannot be converted, the function returns an error value you can check for, instead of silently producing something unexpected.

Dynamic replies

Here is an example that uses an expression to respond with the contact's name in capitalized form:

card Card do
text("Hello @PROPER(contact.name)")
end

Here is a Math Quiz example that uses expressions to generate random numbers and check the response.


card Calculator, "try again!", then: Answer do
calculator_number_1 = rand_between(1, 10)
calculator_number_2 = rand_between(1, 10)

ask("What is @calculator_number_1 * @calculator_number_2 ?")
end

card Answer when calculator == calculator_number_1 * calculator_number_2, then: Calculator do
buttons([Calculator]) do
text("That is correct!")
end
end

card Answer, then: Calculator do
buttons([Calculator]) do
text("""
@calculator is incorrect, the correct answer is:
@(calculator_number_1 * calculator_number_2)
""")
end
end

Dynamic buttons

Going back to our button message example. Let's say the pool of options for buttons isn't known ahead of time, it is possible to generate the button options dynamically using expressions as per this example.

You'll notice that the end result is the same as the previous example but we're looping over a list called options which has the value ["one", "two"].

The & operator in this example indicates an anonymous function and &1 is the first argument given to the anonymous function. Please check the reference for more details.

card Card do
options = ["one", "two"]

d =
buttons(Destination, map(options, &[&1, concatenate("Button ", proper(&1))])) do
text("click a button")
end
end

card Destination do
buttons([Card]) do
text("you clicked @d")
end
end

Reference

There is a long list of available Expressions. You can find all of them here

Here we report some of the most commonly used expressions.

abs()

Returns the absolute value of a number.

card MyCard do
# Sends "The absolute value of -1 is 1"
text("The absolute value of -1 is @abs(-1)")
end

and()

Returns true if and only if all its arguments evaluate to true.

concatenate()

Joins text strings into one text string.

card MyCard do
a = "Hi"
b = "and"
c = "welcome!"
text = concatenate(a, " ", b, " ", c)
# Sends "Hi and welcome!"
text("@text")
end

filter()

Filters a list, i.e. returns only those elements for which the provided expression returns a truthy value.

The expression must be proceeded by the character &, and the current list item being evaluated by the expression is available with the placeholder &1:

card MyCard do
items = ["apple ", "bank ", "app ", "office ", "appointment "]
# Sends "apple app appointment"
text("@filter(items, &has_beginning(&1, \"app\"))")
end

find()

Finds the first element in the list for which the provided expression is truthy.

The expression must be proceeded by the character &, and the current list item being evaluated by the expression is available with the placeholder &1:

card MyCard do
items = [["Hello", "World"], ["Hi", "World"]]
# Result is ["Hi", "World"]
result = find(items, & &1[0] == "Hi")
# Sends "Hi"
text("@result[0]")
end

first_word()

Returns the first word in a string

card MyCard do
input = "One Two Three Four"
# Sends "One"
text("@first_word(input)")
end

fixed()

Formats the given number in decimal format using a period and commas.

card MyCard do
result = 4.209922
# Sends "The result is 4.21"
text("The result is @fixed(4.209922, 2)")
end

has_beginning()

card MyCard do
text = "The Quick Brown Fox"
start = "the quick"
# Sends "true"
text("@has_beginning(text, start)")
end

isbool()

Returns true if the argument is a boolean.

card MyCard do
a = "a"
# Sends "false"
text("@isbool(a)")
end

isnumber()

Returns true if the argument is a number.

card MyCard do
a = 32
# Sends "true"
text("@isnumber(a)")
end

isstring()

Returns true if the argument is a string.

card MyCard do
a = "Hey!"
# Sends "true"
text("@isstring(a)")
end

left()

Returns the first characters in a text string. This is Unicode safe.

card MyCard do
text = "The quick brown fox"
# Sends "The q"
text("@left(text, 5)")
end

lower()

Converts a text string to lowercase.

card MyCard do
text = "HELLO!"
# Sends "hello!"
text("@lower(text)")
end

map()

Returns a list where each element is the result of invoking the provided expression on each corresponding element of the provided list.

The expression must be proceeded by the character &, and the current list item being evaluated by the expression is available with the placeholder &1:

card MyCard do
items = ["a", "b", "c"]
# Sends "ABC"
text("@map(items, &upper(&1))")
end

reduce()

Reduces elements from a list by applying a function and collecting the results in an accumulator.

The expression must be proceeded by the character &. The current list item being evaluated by the expression is available with the placeholder &1 and the accumulator is available within the placeholder &2:

card MyCard do
# Sends 6
text("@reduce(1..3, 0, & &1 + &2)")
end

reject()

Rejects items from list, i.e. returns only those elements for which the provided expression returns a false value.

The expression must be proceeded by the character &, and the current list item being evaluated by the expression is available with the placeholder &1:

card MyCard do
items = ["apple ", "bank ", "app ", "office ", "appointment "]
# Sends "bank office"
text("@reject(items, &has_beginning(&1, \"app\"))")
end

uniq()

Removes duplicate values from a list.

card MyCard do
items = ["A", "B", "B", "C"]
# Result is ["A", "B", "C"]
result = uniq(items)
end

upper()

Converts a text string to UPPERCASE.

card MyCard do
text = "hello!"
# Sends "HELLO!"
text("@upper(text)")
end

with_index()

Wraps each item of the list in a new list with the item itself and its index in the original list.

card MyCard do
items = ["A", "B", "C"]
# Result is [["A", 0], ["B", 1], ["C", 1]]
result = with_index(items)
end