Expressions
An expression is a formula that the Designer evaluates at the time of running the playbook, instead of using a fixed value. It is what allows one activity to use the result of another, build dynamic text, make a calculation or make a decision.
If you’re coming from Excel, the idea is the same: a cell can contain the text Total or the formula =A1+A2. In the Designer, a parameter can contain the text Hola or the expression = "Hola " + nombre.
The two modes of a field
Section titled “The two modes of a field”Each parameter field in the properties panel works in one of two modes. What decides the mode is the first character of the field.
| Mode | When is it activated | What the Designer does |
|---|---|---|
| Literal | The field does not begin with = | Use the text exactly as you wrote it |
| Expression | The field starts with = | Evaluate the content as a formula and use the result |
Modo literal Modo expresión───────────── ──────────────Hola mundo → = "Hola mundo"https://acme.com → = "https://" + dominio30000 → = timeoutBase * 2When do I need an expression?
Section titled “When do I need an expression?”- Reuse the result of an activity. An HTTP GET activity stores its response in a variable
respuesta; to read the status code you type= respuesta.StatusCode. - Build dynamic text.
= "Factura " + numero + " — " + cliente. - Do calculations.
= subtotal * 1.19. - Evaluate a condition (in If, While, Retry…).
= intentos < 3 &&!exito.
If the value never changes (a fixed URL, a fixed route, a fixed number), use literal mode: it’s simpler and faster to read.
Basic syntax
Section titled “Basic syntax”The Zoan Automation expression engine uses C# syntax. You don’t need to know how to program for common formulas, but it’s a good idea to know the rules that most surprise those who come from Excel or other tools.
Referenciar variables
Section titled “Referenciar variables”Write the name of the variable as is:
= nombre= total= respuestaTexto (strings)
Section titled “Texto (strings)”The literal text is always in double quotes "...". Single quotes are reserved for a single character (see padLeft below).
= "Hola mundo"= "El cliente es " + cliente // concatenación con += "Total: " + total + " COP"Numbers and arithmetic operations
Section titled “Numbers and arithmetic operations”= 42= 3.14 // el separador decimal es el punto= precio * cantidad= (subtotal + envio) * 1.19= total / 12Acceso a propiedades y elementos
Section titled “Acceso a propiedades y elementos”Use the dot . to read a property of an object, and the square brackets [...] to access by key or index:
= respuesta.StatusCode // propiedad= respuesta.Headers["Content-Type"] // valor de un diccionario por clave= fila["NombreColumna"] // celda de una fila de DataTable= list[0] // primer elemento de una list (índice 0)= fecha.Year // propiedad de una fechaOperadores
Section titled “Operadores”| Type | Operators | Example |
|---|---|---|
| Arithmetic | + - * / % | = precio * 1.19 |
| Comparison | == != < > <= >= | = total > 1000 |
| Logical | && (y) `\ | \ |
| Text concatenation | + | = "Hola " + nombre |
| Condicional (ternario) | condición? valorSi: valorNo | = total > 0 ? "Cobrar" : "Gratis" |
Built-in functions
Section titled “Built-in functions”The Designer comes with a set of functions ready to use. All are written in lower case (upper, not Upper). When you type = and start typing, the Designer’s autocomplete suggests them with its signature.
| Function | Firma | Description |
|---|---|---|
upper(s) | (string) → string | Convert to uppercase |
lower(s) | (string) → string | Convert to lowercase |
trim(s) | (string) → string | Quita espacios al inicio y al final |
length(s) | (string) → int | Longitud del texto |
replace(s, viejo, nuevo) | (string, string, string) → string | Replaces all occurrences |
split(s, separador) | (string, string) → List | Split text into a list |
contains(s, sub) | (string, string) → bool | true si s contiene sub |
startsWith(s, prefijo) | (string, string) → bool | true if it starts with the prefix |
endsWith(s, sufijo) | (string, string) → bool | true if it ends with the suffix |
substring(s, inicio, largo) | (string, int, int) → string | Extract a portion from inicio |
padLeft(s, ancho, car) | (string, int, char) → string | Fill left to ancho |
padRight(s, ancho, car) | (string, int, char) → string | Fill right to ancho |
= upper(nombre) // "ana" → "ANA"= trim(textoConEspacios)= replace(telefono, "-", "") // quita los guiones= contains(asunto, "URGENTE")= padLeft(toString(numero), 6, '0') // 42 → "000042" (nota: '0' es un char)Numbers
Section titled “Numbers”| Function | Firma | Description |
|---|---|---|
abs(x) | (double) → double | absolute value |
round(x, d) | (double, int) → double | Redondea a d decimales |
floor(x) | (double) → double | Redondea hacia abajo |
ceil(x) | (double) → double | Redondea hacia arriba |
sqrt(x) | (double) → double | Square root |
pow(x, exp) | (double, double) → double | Potencia |
min(a, b) | (double, double) → double | The lesser of two numbers |
max(a, b) | (double, double) → double | The greater of two numbers |
mod(a, b) | (double, double) → double | Remainder of division (module) |
= round(total * 1.19, 2) // IVA con 2 decimales= max(stock, 0) // nunca por debajo de 0Type conversion
Section titled “Type conversion”| Function | Firma | Description |
|---|---|---|
toString(v) | (any) → string | Convert any value to text |
toNumber(v) | (any) → double | Convert to decimal number |
toInt(v) | (any) → int | Convierte a entero |
toBoolean(v) | (any) → bool | Convierte a verdadero/falso |
parseDouble(s) | (string) → double | Read a decimal using the period as a separator |
parseInt(s) | (string) → int | Read an integer |
Validation
Section titled “Validation”| Function | Firma | Description |
|---|---|---|
isNull(v) | (any) → bool | true if value is null |
isEmpty(s) | (string) → bool | true if null or empty string |
isNumber(v) | (any) → bool | true if it can be interpreted as a number |
= isEmpty(correo) ? "sin correo" : correo= isNumber(celda) && toNumber(celda) > 0Fecha y hora
Section titled “Fecha y hora”| Function | Firma | Description |
|---|---|---|
now() | () → DateTime | Fecha y hora local actual |
today() | () → DateTime | Today at 00:00:00 (no time) |
utcNow() | () → DateTime | Fecha y hora actual en UTC |
To add time or format, use methods of type DateTime (see Data Types):
= now()= today().AddDays(-1) // ayer= now().AddHours(2)= now().ToString("yyyy-MM-dd") // "2026-06-06"= now().ToString("dd/MM/yyyy HH:mm")= fecha.YearColecciones (lists y diccionarios)
Section titled “Colecciones (lists y diccionarios)”| Function | Firma | Description |
|---|---|---|
count(e) | (IEnumerable) → int | Number of elements |
join(e, sep) | (IEnumerable, string) → string | Join the elements in a text |
first(e) | (IEnumerable) → any | Primer elemento |
last(e) | (IEnumerable) → any | Last item |
newList() | () → List | Create an empty list |
newDict() | () → Dictionary | Create an empty dictionary |
listAdd(list, v) | (List, any) → List | Adds an element and returns the list |
listGet(list, i) | (List, int) → any | Element at position i |
listCount(list) | (List) → int | Number of elements |
dictSet(d, clave, v) | (Dict, string, any) → Dict | Assigns a key and returns the dictionary |
dictGet(d, clave) | (Dict, string) → any | Value of a key |
= count(correos) // ¿cuántos correos llegaron?= join(nombres, ", ") // ["Ana","Luis"] → "Ana, Luis"= first(filas)DataTable and rows
Section titled “DataTable and rows”When you work with tables (results from Excel, CSV, databases or Google Sheets) you have specific functions. The str, num, and eq functions are important because they safely handle empty cells (DBNull).
| Function | Firma | Description |
|---|---|---|
rowCount(dt) | (DataTable) → int | Number of rows |
colCount(dt) | (DataTable) → int | Number of columns |
firstRow(dt) | (DataTable) → DataRow? | First row, or null if empty |
asRows(dt) | (DataTable) → List<DataRow> | Convert table to list (for use with LINQ) |
selectRows(dt, pred) | (DataTable, Func) → List<DataRow> | Filter rows with a condition |
str(v) | (any) → string | Safe text: "" if cell is null |
num(v) | (any) → double | Safe number: 0 if cell is null |
eq(a, b) | (any, any) → bool | Safe equality between cells (handles nulls) |
= rowCount(tabla)= str(row["Cliente"]) // texto de una celda, sin errores si está vacía= num(fila["Total"]) > 1000= selectRows(tabla, r => str(r["Estado"]) == "Pendiente")Files and paths
Section titled “Files and paths”| Function | Firma | Description |
|---|---|---|
fileExists(ruta) | (string) → bool | true if file exists |
dirExists(ruta) | (string) → bool | true if folder exists |
createDir(ruta) | (string) → string | Create the folder and return its full path |
pathJoin(a, b) | (string, string) → string | Join two parts of the route |
fileName(ruta) | (string) → string | File name of a path |
dirName(ruta) | (string) → string | Folder containing a route |
asset(rel) | (string) → string | Absolute path of a file within your project |
= pathJoin(carpetaSalida, "reporte.xlsx")= asset("plantillas/factura.docx") // ruta a un archivo incluido en el proyecto= fileName(rutaCompleta) // "C:\x\datos.csv" → "datos.csv"Environment variables and credentials
Section titled “Environment variables and credentials”| Function | Firma | Description |
|---|---|---|
env(nombre) | (string) → string? | Read a system environment variable |
credential(nombre) | (string) → Credential | Get a credential from Zoan Cloud or Windows Credential Manager |
= env("USERNAME")= credential("smtp")["password"] // accede a un campo de la credencialRegular expressions (regex)
Section titled “Regular expressions (regex)”To extract or validate unstructured data (an invoice number within a text, an email, a date). They use the.NET dialect.
| Function | Firma | Description |
|---|---|---|
regexMatch(texto, patrón) | (string, string) → bool | true if the pattern appears in the text |
regexFind(texto, patrón) | (string, string) → string | Primera coincidencia, o "" |
regexFindAll(texto, patrón) | (string, string) → List<string> | All matches |
regexExtract(texto, patrón) | (string, string) → List<string> | The groups captured from the first match |
regexGroup(texto, patrón, nombre) | (string, string, string) → string | A named capture group |
regexReplace(texto, patrón, repl) | (string, string, string) → string | Reemplaza cada coincidencia (admite $1, ${nombre}) |
= regexFind(correoTexto, "\d{4}-\d{2}-\d{2}") // primera fecha aaaa-mm-dd= regexMatch(email, "^[^@]+@[^@]+\.[^@]+$") // is this a valid email?= regexGroup(texto, "(?<factura>FAC-\d+)", "factura")Working with lists and tables: LINQ
Section titled “Working with lists and tables: LINQ”For more powerful operations on lists and tables (filter, sort, transform) you can use LINQ, the.NET query syntax, chaining methods with dot. Remember to end with .ToList() when you need a specific list.
// Filtrar una list= numeros.Where(n => n > 100).ToList()
// Ordenar y tomar el primero= clientes.OrderBy(c => c.Saldo).First()
// Filtrar filas de una tabla y contar= asRows(tabla).Where(r => str(r["Estado"]) == "Pendiente").ToList()= count(asRows(tabla).Where(r => num(r["Total"]) > 1000))The available LINQ methods (Where, Select, OrderBy, Any, All, First, ToList…) are listed by type in Data Types.
Autocompletion and type validation
Section titled “Autocompletion and type validation”While you write an expression, the Designer helps you in two ways:
- Autocomplete. When you type
=and start typing, a list appears with the playbook variables and built-in functions, each with its signature. After a dot (variable.), suggests valid members for that variable’s type. - Live validation. The Designer checks the syntax and type of the result. If an expression returns a type other than what the parameter expects (for example, text where a number is required), the field is marked orange as a warning.
Identificadores desconocidos
Section titled “Identificadores desconocidos”If an expression references a name that does not correspond to any variable, at execution it evaluates to null instead of stopping the playbook. This prevents crashes due to iteration variables not yet created, but also means that a typo in a variable name can happen silently. Check the variables panel if an expression returns null unexpectedly.
Common errors
Section titled “Common errors”| Symptom | Causa | Solution |
|---|---|---|
The field saves the text =a+b without calculating | The = is not the first character, or the field is in literal mode | Make sure = is at the start of the field |
Upper(...) / FormatDate(...) no existe | You are using capitalized names. | Functions are in lower case: upper, and for date format use fecha.ToString("...") |
| A text comparison always returns false | You compared DataTable cells with == | Use str(celda) == "valor" or eq(a, b) |
| A decimal is misinterpreted | Data with decimal point | Replace the comma with a period before parseDouble |
\n appears literally in the text | C# escapes don’t work in expressions | Generate the character from a variable/activity |
| Expression returns null for no reason | Nombre de variable mal escrito (identificador desconocido) | Check the exact name in the variables panel |
Next steps
Section titled “Next steps”- Variables — declare and capture data between activities.
- Data types — what properties and methods each type has.
- Debugging — test your expressions by running the playbook locally.