Variable expressions
Use variables and expression blocks in Vetta workflows and sequences to personalise messages, format text, do maths, work with dates, phone numbers and more.
Updated October 25, 2026 · 11 min read
Variables let you use real data from your CRM in your Vetta workflows and sequences. A variable is a placeholder: you put it in a step or a message once, and every time it runs it is swapped for the real value, such as the first name of the contact who just signed up or the name of their company.
Expression blocks go a step further. They let you transform or combine variables with a little logic, so you can format text, do maths, work with dates, parse phone numbers and more before the value is used.
Everything is built from these two pieces.
The two building blocks
Variables
A variable pulls in a value directly from your data, written in double curly braces.
In workflows, variables point to the trigger or to an earlier step, and you rarely type them. Every step shows what's available: the Input panel on the left of the step lists every step that ran before this one. Open the trigger and you'll find the record it brought in, with all of its fields. To use one:
- Click the variable icon in any field and pick the field. If there are several earlier steps, you pick the step first.
- Or drag the field from the Input panel into the field.
The variable drops in as a chip. Related records are there too: open the person, go to their company, and you can use its name, its domain, even its own related records. Use the search box when the list is long.
Switch a field from Fixed to Expression and you'll see what a variable really is: a path in double curly braces. Vetta writes the path for you:
{{trigger.properties.after.name.firstName}}
{{trigger.properties.after.emails.primaryEmail}}
Fields from earlier steps start with that step's id, which the picker also fills in for you. In the editor every value reads null, because nothing has run yet. Once the workflow runs, open that run from Runs and every variable shows the real data it used.
Understanding variables in workflow steps. A variable is a placeholder swapped for the real value on each run. The Input panel on the left lists every earlier step; click the variable icon in a field, pick the step and the field, or drag one in as a chip. Switch to Expression to see the path in double curly brackets. Related records, like a person's company name or domain, are available, and values read null in the editor until a run. Open a run in Runs to see real data.
Transcript
A variable is a placeholder. You put it in a step once, and on every run it's swapped for the real value from that run, like the name of the contact who just signed up. Every step shows you what's available. The Input panel on the left lists every step that ran before this one. Open the trigger, and there's the person it created, with all of their fields. To use one, click the variable icon in any field and pick the field. If there are several earlier steps, you pick the step first. It drops in as a chip. You can also drag a field in from the Input panel. Switch a field to Expression, and you'll see what a variable really is: a path in double curly brackets. Related records are in there too. Open the person, go to their company, and you can use its name, its domain, even its own related records. Use the search box when the list is long. In the editor, every value reads null, because nothing has run yet. Once the workflow runs, open that run from Runs, and every variable shows the real data it used.
Any input in a workflow step can take a variable, not only record fields. In an HTTP Request step, for example, you can drag a contact's id onto the end of the URL, drop their primary email into a header value, or use the variable button to put their job title inside a Raw JSON body. Switching a field to Expression clears it, so drag the variable in again after you switch.
Using variables in any workflow input, not only record fields. The example is an HTTP request sending new contacts to an outside tool: drag the contact's ID onto the end of the URL, drop the primary email into a header, switch to POST, and in Raw JSON use the variable button to put the job title inside quotes. Switching a field to Expression shows the double curly bracket path. In a run, the step's input shows the real values. Use it for API calls.
Transcript
Any input in a workflow step can take a variable, not only record fields. This HTTP request sends every new contact to an outside billing tool. Open the step: on the left, the Input panel lists what the earlier steps produced. Type the start of the URL, then drag the contact's ID onto the end of it. It lands as a chip. Headers work the same way: drop the primary email into a header value. Switch the method to POST, and a body appears. In Raw JSON, use the variable button to put the job title right inside the quotes. To see the full path, switch a field to Expression. That clears the field, so drag the ID in again, and now it's written out in double curly brackets. Once the workflow is active, every run swaps in that run's real values: open a run, click the step, and its input shows the ID and email it used.
In sequence messages, variables point to the person being contacted and the sender, and you insert them from the variable menu in the message editor:
{{currentPerson.firstName}}
{{currentPerson.company.name}}
{{currentSender.email.firstName}}
The examples below use the sequence style for readability. In a workflow, replace the variable with the path from the Input panel; everything inside {% %} works the same way in both.
Expression blocks
An expression lets a field work out its value instead of just copying one. In a workflow, switch the field from Fixed to Expression, then wrap your logic in a {% %} block. Inside, it is plain JavaScript, and variables still use the same {{ }} syntax:
{% ... your logic here ... %}
For example, this makes a contact's first name uppercase:
{% {{currentPerson.firstName}}.toUpperCase() %}
Three everyday examples: .trim() after a first name removes stray spaces, {{phone}} || 'No phone' gives a fallback when the phone is empty, and Date.now() minus the creation date, divided by the milliseconds in a day, gives how many days a contact has been in your CRM. Check the result on a run: an expression with a mistake doesn't stop the workflow, it just comes out blank.
Making a workflow field calculate its value. Switch the field from Fixed to Expression and wrap logic in a curly bracket and percent sign; inside it is plain JavaScript and variables keep double curly brackets. Examples trim a first name, fall back to No phone with two pipes, and count days since creation using Date.now(). An expression with a mistake does not stop the workflow, it comes out blank, as with a misspelled floor. Use it for text, maths and dates.
Transcript
An expression lets a field work out its value, instead of just copying one. Switch the field from Fixed to Expression, and wrap your logic in a curly bracket and a percent sign. Inside, it's plain JavaScript, and variables still use double curly brackets. In the title, drag in the first name and add.trim(), so stray spaces disappear. In the body, drag in the phone number, then two pipes and a fallback: when the phone is empty, the task reads "No phone". Maths and dates work too: take Date.now(), subtract the creation date, and divide by the milliseconds in a day, to get how many days the contact has been in your CRM. Then check it on a run. An expression with a mistake doesn't stop the workflow. It just comes out blank. Here the days are missing, because floor is misspelled. Every Wednesday, Jonas asks Claude to polish his call tasks. Claude writes three expressions and fixes the blank one. Claude, ChatGPT or any assistant you connect to Vetta works on your real workspace, so test every expression, then give it a bigger job each week. In Vetta, read the test run, then activate it yourself.
Never put quotes around a variable inside an expression block. Vetta inserts each {{variable}} as a ready-quoted value, so write {% {{currentPerson.firstName}}.trim() %}, not {% "{{currentPerson.firstName}}".trim() %}. The quoted version breaks the expression, and it silently comes out blank.
Text manipulation
Text expressions let you reshape a value before it appears in your message, such as changing capitalisation, trimming spaces, or pulling out part of a string.
| What you want | Expression |
|---|---|
| First name only (from full name) | {% {{currentPerson.name}}.split(' ')[0] %} |
| All caps | {% {{currentPerson.firstName}}.toUpperCase() %} |
| All lowercase | {% {{currentPerson.firstName}}.toLowerCase() %} |
| Remove extra spaces | {% {{currentPerson.firstName}}.trim() %} |
| First N characters | {% {{currentPerson.firstName}}.slice(0, 3) %} |
| Combine first and last name | {% {{currentPerson.firstName}} + ' ' + {{currentPerson.lastName}} %} |
| Email domain only | {% {{currentPerson.email}}.split('@')[1] %} |
| Replace a word | {% {{currentPerson.bio}}.replace('Manager', 'Leader') %} |
Numbers and math
Math expressions let you calculate values as the message is built, which is useful for converting stored amounts, applying multipliers, or rounding figures for display.
| What you want | Expression |
|---|---|
| Divide (e.g. convert micros to dollars) | {% {{deal.amountMicros}} / 1000000 %} |
| Multiply | {% {{deal.amount}} * 1.2 %} |
| Round to nearest whole number | {% Math.round({{deal.amount}}) %} |
| Round down | {% Math.floor({{deal.amount}}) %} |
| Round up | {% Math.ceil({{deal.amount}}) %} |
| Show 2 decimal places | {% {{deal.amount}}.toFixed(2) %} |
| Larger of two values | {% Math.max({{deal.amount}}, {{deal.minAmount}}) %} |
Conditional logic
Conditional expressions let a message change depending on the data. They use a ternary, which reads as condition ? value if true : value if false.
| What you want | Expression |
|---|---|
| Fallback if empty | {% {{currentPerson.phone}} || 'N/A' %} |
| Label based on a value | {% {{currentPerson.age}} >= 18 ? 'Adult' : 'Minor' %} |
| Multiple conditions | {% {{deal.stage}} === 'Won' ? 'Closed' : {{deal.stage}} === 'Lost' ? 'Dead' : 'Active' %} |
| Use another variable as fallback | {% {{currentPerson.nickname}} || {{currentPerson.firstName}} %} |
Dates
Date expressions let you insert the current date, read the year or month, or format a date stored on a record.
| What you want | Expression |
|---|---|
| Today's date (ISO format) | {% new Date().toISOString().slice(0, 10) %} |
| Current year | {% new Date().getFullYear() %} |
| Current month (1 to 12) | {% new Date().getMonth() + 1 %} |
| Format a stored date field | {% new Date({{deal.closedAt}}).toLocaleDateString() %} |
| Days since a date | {% Math.floor((Date.now() - new Date({{person.createdAt}})) / 86400000) %} |
| Check if a date is in the past | {% new Date({{deal.dueAt}}) < new Date() ? 'Overdue' : 'On track' %} |
Arrays
Some fields, such as webhook payloads, contain a list of items rather than a single value. Array expressions let you pull out a specific item or combine several of them. You can use dot notation or bracket notation to access array elements.
| What you want | Expression |
|---|---|
| First item in an array | {{trigger.payload.attendees.0.name}} |
| Second item | {{trigger.payload.attendees.1.email}} |
| First item via bracket notation | {{trigger.payload.attendees[0].name}} |
| Last item (dynamic) | {% {{trigger.payload.attendees}}[{{trigger.payload.attendees}}.length - 1].name %} |
| All names joined | {% {{trigger.payload.attendees}}.map(a => a.name).join(', ') %} |
| Find item by condition | {% {{trigger.payload.attendees}}.find(a => a.noShow)?.email %} |
Phone numbers
Phone expressions let you break a phone number into its separate parts. Use parsePhoneNumber(value), which returns null when the number is invalid, so pair it with ?. to avoid errors.
| What you want | Expression |
|---|---|
Calling code only (e.g. 91) | {% parsePhoneNumber({{person.phone}})?.countryCallingCode %} |
| National number only (no code) | {% parsePhoneNumber({{person.phone}})?.nationalNumber %} |
| Full international format | {% parsePhoneNumber({{person.phone}})?.formatInternational() %} |
Calling code with + prefix | {% '+' + parsePhoneNumber({{person.phone}})?.countryCallingCode %} |
| Check if number is valid | {% parsePhoneNumber({{person.phone}})?.isValid() ? 'Valid' : 'Invalid' %} |
| Fallback if unparseable | {% parsePhoneNumber({{person.phone}})?.nationalNumber || {{person.phone}} %} |
Regular expressions
Regular expressions let you match or extract a pattern inside a value, such as the text before the @ in an email address.
| What you want | Expression |
|---|---|
Extract text before @ in an email | {% {{currentPerson.email}}.match(/(.+)@/)[1] %} |
| Remove all spaces | {% {{currentPerson.phone}}.replace(/\s+/g, '') %} |
| Check if value matches a pattern | {% /^\+\d+/.test({{currentPerson.phone}}) ? 'International' : 'Local' %} |
Using expressions inside a message
Expression blocks can be placed anywhere in your message body or subject line, alongside regular text and variables. Vetta evaluates each block when the message is sent and replaces it with the result.
Hi {% {{currentPerson.firstName}}.split(' ')[0] %},
I noticed {{currentPerson.company.name}} has been growing fast.
Your deal value is ${% {{deal.amountMicros}} / 1000000 %}.
Looking forward to connecting!
What is available inside an expression
Expressions run in a sandbox. Only these globals are available:
Math: all standard math functionsDate: date creation and formattingRegExp: regular expressionsparsePhoneNumber: phone number parsing (see above)
Plus the standard methods of the values themselves (strings, numbers, arrays).
What is not allowed
To keep your workspace secure, the following are blocked inside an expression:
- Running system commands or accessing files
- Making network requests
- Accessing internal server or environment data
If an expression is blocked or contains an error, the block is left blank rather than filled with incorrect data, so a broken expression never sends bad information to your contact. The same goes for a variable that points to nothing: it becomes empty text. Check one real run after every change.
Common questions
Why is my variable blank in the step or message?
A variable that points to nothing becomes empty text, and an expression with a mistake comes out blank instead of failing, so the run still shows as completed. Open a real run from Runs and check the value the step actually used. See Expression blocks.
Why does my expression come out blank when the syntax looks right?
Check for quotes around a variable. Vetta inserts each {{variable}} as a ready-quoted value, so {% "{{currentPerson.firstName}}".trim() %} breaks and silently comes out blank; write {% {{currentPerson.firstName}}.trim() %} instead. See Expression blocks.
Why do variables show null in the editor?
Nothing has run yet, so there's no data to show. Once the workflow runs, open that run and every variable shows the real data it used. See Variables.
Why did my field clear when I switched it to Expression?
Switching a field from Fixed to Expression clears it. Drag the variable in again from the Input panel after you switch. See Variables.
Can I use expressions in sequence messages as well as workflows?
Yes. Expression blocks go anywhere in the message body or subject line, next to regular text and variables, and are evaluated when the message is sent. See Using expressions inside a message.
How do I show a deal amount in dollars instead of micros?
Divide by 1,000,000: {% {{deal.amountMicros}} / 1000000 %}. See Numbers and math.
Why does my phone number expression fail on some contacts?
parsePhoneNumber(value) returns null when the number is invalid, so pair it with ?. and give a fallback, for example {% parsePhoneNumber({{person.phone}})?.nationalNumber || {{person.phone}} %}. See Phone numbers.
Can I call an API or use a library inside an expression?
No. Expressions run in a sandbox with only Math, Date, RegExp and parsePhoneNumber, plus the standard methods of strings, numbers and arrays. Network requests, files and system data are blocked. See What is available inside an expression.
Key outcome
You can now bring any CRM value into a workflow step or a sequence message, and reshape it on the way: tidy names, fallbacks for empty fields, maths, dates, phone numbers and patterns. Your automations and messages use each contact's real data without anyone typing it in.
Still stuck?
We reply the same business day, 9:00 to 17:00 CET, Monday to Friday.