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)istruewhenageis"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