Skip to main content

Transformers

Transformers are pipe functions that modify injected data. Chain them after a provider expression using |.

%provider:path|transformer1(args)|transformer2(args)%
String arguments must use double quotes

Transformer arguments must use " (double quotes), never ' (single quotes). To avoid breaking the YAML string, wrap the entire value in single quotes:

value: '%provider:path|transformer("arg1","arg2")%'

If you must use double quotes for the YAML string, escape the inner quotes with \":

value: "%provider:path|transformer(\"arg1\",\"arg2\")%"
No nested data injection

Data injection does not support nesting %...% inside another %...% expression. Each %...% is resolved independently. If you need to use a dynamic value as an argument, store it first with storeValue and access it in a separate node.


Quick reference

String transformers

TransformerDescription
replaceReplace substrings using regex
trimRemove leading/trailing whitespace
splitSplit a string into an array
sliceExtract a substring by index
toUpperCaseConvert to uppercase
toLowerCaseConvert to lowercase
parseIntParse string to integer
jsonParseParse a JSON string into an object
parseXmlParse an XML string into an object
encodeURIURL-encode a string
decodeURIURL-decode a string
formatPhoneFormat a phone number
hasWordsCheck if a string contains any of the given words

Array transformers

TransformerDescription
columnExtract a field from each item
joinJoin items into a string
mapRename/remap fields in each item
filterKeep items matching an expression
sortSort items by field(s)
reverseReverse the order of items
flattenFlatten nested arrays

Date transformers

TransformerDescription
formatDateFormat a date value using Luxon
parseDateParse a date string into a Date object

Mixed / universal transformers

TransformerDescription
lengthGet length of a string or array
toStringConvert any value to a string
toJsonSerialize a value to JSON
typeofGet the JavaScript type of a value
getAccess a nested property from an object by path
pickKeep only specified keys from an object
omitRemove specified keys from an object
hbTplRender a Handlebars template

String transformers

formatPhone

Formats a phone number into a specific format.

|formatPhone("format", "country")
ArgumentRequiredDescriptionValues
formatYesOutput format"smart", "e164"
countryYesISO country code"IL", "US", etc.

Examples

# Smart format — local-style with dashes
value: '%chat:phone|formatPhone("smart","IL")%'
# "972521234567" → "052-123-4567"
# E.164 international format
value: '%chat:phone|formatPhone("e164","IL")%'
# "052-123-4567" → "+972521234567"
# Chain with replace to strip dashes
value: '%chat:phone|formatPhone("smart","IL")|replace("-","","g")%'
# "972521234567" → "052-123-4567" → "0521234567"

replace

Replaces occurrences of a substring using a regular expression.

|replace("search", "replacement", "flags")
ArgumentRequiredDescription
searchYesString or regex pattern to find
replacementYesString to replace with
flagsNoRegex flags — "g" for global (all occurrences)

Examples

# Remove all dashes
value: '%chat:phone|formatPhone("smart","IL")|replace("-","","g")%'
# "052-123-4567" → "0521234567"
# Strip protocol from URL
value: '%state:node.get_url.response.signed_url|replace("https://","")%'
# "https://storage.example.com/file.pdf" → "storage.example.com/file.pdf"
# Replace newlines with spaces
value: '%state:node.ask_reason.text|replace("\n"," ","g")%'
# "line one\nline two\nline three" → "line one line two line three"

trim

Removes leading and trailing whitespace.

|trim

Takes no arguments.

Example

value: "%state:node.user_input.text|trim%"
# " hello world " → "hello world"

split

Splits a string into an array by a separator.

|split("separator")
ArgumentRequiredDescription
separatorYesThe delimiter to split on

Examples

# Split comma-separated tags into an array
value: '%state:node.ask_tags.text|split(",")%'
# "red,green,blue" → ["red", "green", "blue"]
# Split by newline
value: '%state:node.ask_address.text|split("\n")%'
# "Street 1\nCity\nCountry" → ["Street 1", "City", "Country"]

slice

Extracts a substring by start and end index (like JavaScript String.slice()).

|slice(start, end)
ArgumentRequiredDefaultDescription
startNo0Start index (0-based). Negative values count from the end.
endNostring lengthEnd index (exclusive). Negative values count from the end.

Examples

# Get first 10 characters
value: "%state:node.long_text.text|slice(0,10)%"
# "0123456789extra" → "0123456789"
# Get last 4 characters (e.g. last 4 digits)
value: "%chat:phone|slice(-4)%"
# "972521234567" → "4567"
# Skip the first 3 characters
value: "%state:store.code|slice(3)%"
# "IL-12345" → "12345"

toUpperCase

Converts a string to uppercase.

|toUpperCase

Takes no arguments.

Example

value: "%state:node.ask_name.text|toUpperCase%"
# "john" → "JOHN"

toLowerCase

Converts a string to lowercase.

|toLowerCase

Takes no arguments.

Example

value: "%state:node.ask_email.text|toLowerCase|trim%"
# " John@Email.COM " → " john@email.com " → "john@email.com"

parseInt

Converts a string to an integer.

|parseInt

Takes no arguments.

Examples

value: "%chat:resolvedUpdateTime|toString|parseInt%"
# "1679500800000" → 1679500800000
# Get timestamp from 5 days ago as integer
value: '%time:now-5d("x")|parseInt%'
# "1710633600000" → 1710633600000

jsonParse

Parses a JSON string into an object. Useful when an API returns JSON as a string field.

|jsonParse

Takes no arguments.

Example

value: "%state:store.apiPayload|jsonParse%"
# "{\"name\":\"Alice\",\"age\":30}" → {name: "Alice", age: 30}

parseXml

Parses an XML string into a JavaScript object.

|parseXml

Takes no arguments.

Example

value: "%state:node.soap_call.response.body|parseXml%"
# "<user><name>Alice</name><age>30</age></user>" → {user: {name: "Alice", age: 30}}

encodeURI

URL-encodes a string. Useful when injecting values into query strings.

|encodeURI

Takes no arguments.

Example

params:
url: "https://api.example.com/search?q=%state:node.ask_query.text|encodeURI%"
# "hello world" → "hello%20world"

decodeURI

URL-decodes a string. Reverses the effect of encodeURI.

|decodeURI

Takes no arguments.

Example

value: "%state:node.get_callback.response.return_url|decodeURI%"
# "hello%20world%3F" → "hello world?"

hasWords

Checks if a string contains at least one of the given words. Returns true or false.

|hasWords(["word1","word2"], caseInsensitive)
ArgumentRequiredDefaultDescription
wordsYesArray of words to search for
caseInsensitiveNofalseSet to true for case-insensitive matching

Examples

# Check if the user's message contains certain keywords (case insensitive)
value: '%state:node.user_msg.text|hasWords(["cancel","ביטול"],true)%'
# "I want to Cancel my order" → true
# Case-sensitive check
value: '%state:node.ask_reason.text|hasWords(["URGENT","ASAP"])%'
# "this is urgent" → false (case-sensitive, "urgent" ≠ "URGENT")
# "this is URGENT" → true
tip

hasWords returns a boolean, making it useful as a value in matchExpression or switch conditions.


Array transformers

column

Extracts a single field from each object in an array, returning a new array of those values.

|column("fieldName")
ArgumentRequiredDescription
fieldNameYesThe field to extract from each item

Examples

# Get the text of all recent incoming messages
value: '%messages:latest(5,-1,"in")|column("text")%'
# [{text: "hi", ...}, {text: "help", ...}] → ["hi", "help"]
# Extract order IDs from API response
value: '%state:node.get_orders.response.orders|column("id")%'
# [{id: "A1", name: "Order 1"}, {id: "A2", name: "Order 2"}] → ["A1", "A2"]
# Get a list of names and join them
value: '%state:store.contacts|column("name")|join(", ")%'
# [{name: "Alice"}, {name: "Bob"}] → ["Alice", "Bob"] → "Alice, Bob"

join

Joins array items into a single string with a separator.

|join("separator")
ArgumentRequiredDescription
separatorYesString to place between items

Examples

# Join with newline
value: '%state:store.items|join("\n")%'
# ["Milk", "Bread", "Eggs"] → "Milk\nBread\nEggs"
# Join with comma and space
value: '%state:node.get_tags.response.tags|column("name")|join(", ")%'
# [{name: "vip"}, {name: "new"}] → ["vip", "new"] → "vip, new"

map

Remaps fields in each object of an array. Creates new objects with renamed keys. Useful for building choice lists from API data.

|map("sourceField::targetField", "sourceField2::targetField2", ...)

Each argument is a "source::target" pair. The source is the field name in the original object, and target is the field name in the output object.

Examples

# Map CRM order data into a choice-compatible list
choices: '%state:node.open_orders.response.orders|map("summary::title","Order_ID::id")%'
# [{summary: "Laptop", Order_ID: "A1"}, {summary: "Phone", Order_ID: "A2"}]
# → [{title: "Laptop", id: "A1"}, {title: "Phone", id: "A2"}]
tip

The ::title mapping is what appears to the user in a choice list. The ::id mapping is the value stored when the user selects it.

# Map services into choices
choices: '%state:node.get_services.response.services|map("service_name::title","service_id::id")%'
# [{service_name: "Haircut", service_id: "5"}, {service_name: "Color", service_id: "8"}]
# → [{title: "Haircut", id: "5"}, {title: "Color", id: "8"}]

filter

Filters an array, keeping only items that match a Filtrex expression. Multiple expressions act as OR conditions (item matches if any expression is true). Non-object items are always excluded.

|filter("expression1", "expression2", ...)
ArgumentRequiredDescription
expressionYes (at least one)A Filtrex expression to match against each item

Supported operators in expressions: ==, !=, >, >=, <, <=, and, or, not, plus boolean fields (truthy check).

Examples

# Keep only active items (truthy check on boolean field)
value: '%state:store.users|filter("active")%'
# [{name: "Alice", active: true}, {name: "Bob", active: false}]
# → [{name: "Alice", active: true}]
# Numeric comparison
value: '%state:node.get_orders.response.orders|filter("score >= 90")%'
# [{id: 1, score: 85}, {id: 2, score: 92}, {id: 3, score: 95}]
# → [{id: 2, score: 92}, {id: 3, score: 95}]
# String comparison
value: '%state:store.employees|filter("department == \"Engineering\"")%'
# [{name: "Alice", department: "Engineering"}, {name: "Bob", department: "Sales"}]
# → [{name: "Alice", department: "Engineering"}]
# Combined conditions with AND
value: '%state:store.users|filter("age > 27 and active")%'
# [{name: "Alice", age: 25, active: true}, {name: "Bob", age: 30, active: false}, {name: "Charlie", age: 35, active: true}]
# → [{name: "Charlie", age: 35, active: true}]
# Multiple expressions (OR logic) — matches Engineering OR age >= 35
value: '%state:store.employees|filter("department == \"Engineering\"","age >= 35")%'
# [{name: "Alice", age: 25, department: "Engineering"}, {name: "Bob", age: 30, department: "Sales"}, {name: "Charlie", age: 35, department: "Marketing"}]
# → [{name: "Alice", age: 25, department: "Engineering"}, {name: "Charlie", age: 35, department: "Marketing"}]

sort

Sorts an array of objects by one or more fields (ascending). Uses lodash sortBy under the hood.

|sort("field1", "field2", ...)
ArgumentRequiredDescription
fieldYes (at least one)Field name to sort by. Supports dot-notation for nested fields.

When multiple fields are given, items are sorted by the first field, then ties are broken by the second field, and so on.

Examples

# Sort orders by date
value: '%state:node.get_orders.response.orders|sort("created_at")%'
# [{id: 3, created_at: "2025-03-01"}, {id: 1, created_at: "2025-01-15"}, {id: 2, created_at: "2025-02-10"}]
# → [{id: 1, created_at: "2025-01-15"}, {id: 2, created_at: "2025-02-10"}, {id: 3, created_at: "2025-03-01"}]
# Sort by department, then by name within each department
value: '%state:store.employees|sort("department","name")%'
# [{department: "Sales", name: "Zoe"}, {department: "Engineering", name: "Charlie"}, {department: "Engineering", name: "Alice"}]
# → [{department: "Engineering", name: "Alice"}, {department: "Engineering", name: "Charlie"}, {department: "Sales", name: "Zoe"}]
tip

sort always sorts ascending. To get descending order, chain with reverse:

value: '%state:store.orders|sort("created_at")|reverse%'
# (oldest → newest) → (newest → oldest)

reverse

Reverses the order of items in an array.

|reverse

Takes no arguments.

Examples

# Get messages oldest-first (reverse the default newest-first)
value: "%messages:latest(10)|reverse%"
# [newest, ..., oldest] → [oldest, ..., newest]
# Sort descending by combining sort + reverse
value: '%state:store.scores|sort("value")|reverse%'
# [{value: 50}, {value: 80}, {value: 30}] → [{value: 30}, {value: 50}, {value: 80}] → [{value: 80}, {value: 50}, {value: 30}]

flatten

Flattens nested arrays to a specified depth.

|flatten(depth)
ArgumentRequiredDefaultDescription
depthNo1How many levels deep to flatten

Examples

# Flatten one level (default)
value: "%state:store.nestedItems|flatten%"
# [[1, 2], [3, 4], [5]] → [1, 2, 3, 4, 5]
# Flatten deeply nested arrays
value: "%state:store.deeplyNested|flatten(3)%"
# [[[1, 2]], [[3, [4]]]] → [1, 2, 3, 4]

Date transformers

formatDate

Formats a date value (string, number, or Date) into a formatted string using Luxon tokens.

|formatDate("format", "timezone")
ArgumentRequiredDefaultDescription
formatNo"yyyy-MM-dd"Luxon format string, or "iso" for ISO 8601
timezoneNo"UTC"Timezone — IANA name, "ist" (alias for Asia/Jerusalem), or "UTC"

Examples

# Format a timestamp from an API response
value: '%state:node.get_appointment.response.date|formatDate("dd/MM/yyyy","ist")%'
# "2025-06-15T10:30:00Z" → "15/06/2025"
# ISO format
value: '%state:store.createdAt|formatDate("iso")%'
# 1718451000000 → "2025-06-15T10:30:00.000Z"
# Full date and time in Israel timezone
value: '%state:node.get_event.response.start|formatDate("dd.MM.yyyy HH:mm","ist")%'
# "2025-06-15T10:30:00Z" → "15.06.2025 13:30"
# Just the time
value: '%state:store.scheduledTime|formatDate("HH:mm","ist")%'
# "2025-06-15T10:30:00Z" → "13:30"
info

This transformer is different from the time provider. Use the time provider to get the current time with arithmetic (e.g. %time:now-5d%). Use formatDate to reformat an existing date/timestamp value from state or an API response.


parseDate

Parses a date string into a Date object, using a specified format and timezone. Useful when you receive dates in a non-standard format and need to convert them.

|parseDate("format", "timezone")
ArgumentRequiredDefaultDescription
formatNo"yyyy-MM-dd"Luxon format string to parse with, or "iso" for ISO 8601
timezoneNo"utc"Timezone to interpret the input in — IANA name, "ist", or "utc"

Examples

# Parse a date string in dd/MM/yyyy format
value: '%state:node.ask_date.text|parseDate("dd/MM/yyyy","ist")%'
# "15/06/2025" → Date(2025-06-14T21:00:00.000Z)
# Parse an ISO date string
value: '%state:store.dateString|parseDate("iso")%'
# "2025-06-15T10:30:00Z" → Date(2025-06-15T10:30:00.000Z)
# Parse and then reformat
value: '%state:node.ask_date.text|parseDate("dd/MM/yyyy","ist")|formatDate("yyyy-MM-dd","UTC")%'
# "15/06/2025" → Date → "2025-06-14"

Mixed / universal transformers

length

Returns the length of a string or array.

|length

Takes no arguments. Works on both strings and arrays.

Examples

# Check the length of user input (use in matchExpression)
check_id_length:
type: func
func_type: system
func_id: matchExpression
params:
expression: 'length == 9'
length: "%state:node.ask_id.text|length%"
on_complete: valid_id
on_failure: invalid_id
# "123456789" → 9 (matches expression)
# "12345" → 5 (fails expression)
# Get number of items in an array
value: "%state:node.get_orders.response.orders|length%"
# [{...}, {...}, {...}] → 3
# Check message length
value: "%state:node.ask_text.text|length%"
# "Hello" → 5

toString

Converts any value to its string representation.

|toString

Takes no arguments.

Examples

value: "%state:store.lastOutMsg.0.timestamp|toString|parseInt%"
# 1679500800000 → "1679500800000" → 1679500800000
value: "%chat:resolvedUpdateTime|toString%"
# 1679500800000 → "1679500800000"

toJson

Serializes any value to a JSON string.

|toJson(prettyPrint)
ArgumentRequiredDefaultDescription
prettyPrintNofalseSet to true for indented (pretty-printed) output

Examples

# Serialize an object to a compact JSON string (e.g. for a webhook body)
value: "%state:store.userData|toJson%"
# {name: "Alice", age: 30} → '{"name":"Alice","age":30}'
# Pretty-print for a readable log or message
value: "%state:node.api_call.response|toJson(true)%"
# {name: "Alice", age: 30} → '{
# "name": "Alice",
# "age": 30
# }'

typeof

Returns the JavaScript type of the value ("string", "number", "boolean", "object", "undefined", etc.).

|typeof

Takes no arguments.

Examples

value: "%state:store.apiResult|typeof%"
# {name: "Alice"} → "object"
# "hello" → "string"
# 42 → "number"
# true → "boolean"

get

Accesses a nested property from an object using dot notation or separate path segments (powered by lodash.get) - Commonly used for when the property name is in Hebrew which breaks regular data injection (can't use %chat:crmData.סטטוס% for example). Returns undefined if the path does not exist.

|get("path")
ArgumentRequiredDescription
pathYes (at least one)Property path string. Use dot notation for nested access (e.g. "user.name") or pass multiple arguments for each segment.

Examples

# Access a top-level crmData field
value: '%chat:crmData|get("status")%'
# {status: "active", name: "Alice"} → "active"
# Access a nested field using dot notation
value: '%state:node.api_call.response|get("user.profile.email")%'
# {user: {profile: {email: "a@b.com"}}} → "a@b.com"
# Use in switchNode to route on a CRM field value
route_by_type:
type: func
func_type: system
func_id: switchNode
params:
input: '%chat:crmData|get("type")%'
cases:
"vip": vip_flow
"regular": regular_flow
empty: unknown_flow
on_complete: unknown_flow
# Access an array element by index
value: '%state:store.items|get("0.name")%'
# [{name: "First"}, {name: "Second"}] → "First"

pick

Keeps only the specified keys from an object, discarding everything else (powered by lodash.pick).

|pick("key1","key2",...)
ArgumentRequiredDescription
keyYes (at least one)Key name to keep. Pass multiple arguments to keep multiple keys.

Examples

# Keep only name and email from a CRM response
value: '%state:node.get_contact.response|pick("name","email")%'
# {name: "Alice", email: "a@b.com", internal_id: 99, score: 5} → {name: "Alice", email: "a@b.com"}
# Trim a crmData object before sending to a webhook
value: '%chat:crmData|pick("id","name","phone")%'
# {id: "123", name: "Alice", phone: "052...", deepLink: "...", status: "2"} → {id: "123", name: "Alice", phone: "052..."}

omit

Removes specified keys from an object, keeping everything else (powered by lodash.omit).

|omit("key1","key2",...)
ArgumentRequiredDescription
keyYes (at least one)Key name to remove. Pass multiple arguments to remove multiple keys.

Examples

# Remove internal fields before sending to a webhook
value: '%chat:crmData|omit("deepLink","status")%'
# {id: "123", name: "Alice", deepLink: "https://...", status: "2"} → {id: "123", name: "Alice"}
# Strip auth fields from an API response before storing
value: '%state:node.login.response|omit("token","refresh_token")%'
# {userId: "5", name: "Alice", token: "abc", refresh_token: "xyz"} → {userId: "5", name: "Alice"}

hbTpl

Renders a Handlebars template against the input data. The input must be an object or array.

|hbTpl("template", escape, skipNonObject)
ArgumentRequiredDefaultDescription
templateYesHandlebars template string
escapeNofalseIf true, HTML-escapes output. Default (false) means no escaping.
skipNonObjectNotrueIf true (default), returns empty string for non-object inputs instead of throwing an error.

Built-in helpers

{\{#each .}} — Iterate

Standard Handlebars iteration over arrays:

{{#each .}}
{{this.name}}: {{this.value}}
{{/each}}

{{date source format timezone}} — Format dates

Formats a date/timestamp inside the template.

ArgumentRequiredDefaultDescription
sourceYesTimestamp (string, number, or Date)
formatNo"d.L.y, HH:mm:ss"Luxon format string
timezoneNoCustomer default timezoneIANA timezone or the customer's configured default
{{date timestamp}}
{{date timestamp "dd/MM/yyyy"}}
{{date timestamp "HH:mm" "Asia/Jerusalem"}}

{\{#when left 'op' right}} — Conditional

Compares two values using an operator.

OperatorDescription
eqEqual (===)
noteqNot equal (!==)
gtGreater than (numbers only)
gteqGreater than or equal (numbers only)
ltLess than (numbers only)
lteqLess than or equal (numbers only)
orLogical OR
andLogical AND
%Modulo equals zero (numbers only)

Supports {{else}} blocks:

{{#when direction 'eq' 'incoming'}}Customer{{else}}Agent{{/when}}

{{provide 'provider' 'key'}} — Access other providers

Fetches data from any data injection provider inside the template:

{{provide 'chat' 'title'}}
{{provide 'chat' 'phone'}}

{{log message data}} — Debug logging

Logs to the server console (for debugging). Supports an optional severity hash parameter.

{{log "Processing item" this severity="debug"}}

Full examples

# Format a conversation log from recent messages
value: |
%messages:latest(5,1,"any","text")|hbTpl("
{{#each .}}
[{{date timestamp}}] {{#when direction 'eq' 'incoming'}}{{provide 'chat' 'title'}}{{else}}{{agent}}{{/when}}:
{{text}}

{{/each}}
")%
# Input: [{direction: "incoming", text: "Hi", timestamp: 1718451000000, ...}, {direction: "outgoing", text: "Hello!", agent: "Support", timestamp: 1718451060000, ...}]
# Output:
# [15.6.2025, 13:30:00] John:
# Hi
#
# [15.6.2025, 13:31:00] Support:
# Hello!
# Build a summary of order items
value: |
%state:node.get_order.response.items|hbTpl("
{{#each .}}
• {{name}} x{{quantity}} — ₪{{price}}
{{/each}}
")%
# Input: [{name: "Laptop", quantity: 1, price: 3500}, {name: "Mouse", quantity: 2, price: 80}]
# Output:
# • Laptop x1 — ₪3500
# • Mouse x2 — ₪80
# Conditional content based on status
value: |
%state:node.check_status.response|hbTpl("
{{#when status 'eq' 'approved'}}Your request was approved!{{else}}Your request is still pending.{{/when}}
")%
# Input: {status: "approved"} → "Your request was approved!"
# Input: {status: "pending"} → "Your request is still pending."

Chaining transformers

You can chain multiple transformers left to right. Each transformer receives the output of the previous one:

# Format phone → remove dashes
value: '%chat:phone|formatPhone("smart","IL")|replace("-","","g")%'
# "972521234567" → "052-123-4567" → "0521234567"
# Convert to string → parse as integer
value: "%chat:resolvedUpdateTime|toString|parseInt%"
# 1679500800000 → "1679500800000" → 1679500800000
# Get timestamp from 5 days ago as integer
value: '%time:now-5d("x")|parseInt%'
# "1710633600000" → 1710633600000
# Lowercase and trim user input
value: "%state:node.ask_email.text|toLowerCase|trim%"
# " John@Email.COM " → " john@email.com " → "john@email.com"
# Sort orders by date descending, extract titles, join
value: '%state:store.orders|sort("date")|reverse|column("title")|join("\n")%'
# [{title: "B", date: "2025-01"}, {title: "A", date: "2025-03"}, {title: "C", date: "2025-02"}]
# → sort → [{title: "B",...}, {title: "C",...}, {title: "A",...}]
# → reverse → [{title: "A",...}, {title: "C",...}, {title: "B",...}]
# → column → ["A", "C", "B"]
# → join → "A\nC\nB"
# Parse a date from user input, then reformat it for an API
value: '%state:node.ask_date.text|parseDate("dd/MM/yyyy","ist")|formatDate("yyyy-MM-dd","UTC")%'
# "15/06/2025" → Date → "2025-06-14"
# Filter active users, extract names, join as comma-separated list
value: '%state:store.users|filter("active")|column("name")|join(", ")%'
# [{name: "Alice", active: true}, {name: "Bob", active: false}, {name: "Charlie", active: true}]
# → [{name: "Alice", active: true}, {name: "Charlie", active: true}]
# → ["Alice", "Charlie"]
# → "Alice, Charlie"