# Expressions

What you can write between {{ and }}, and what you get back.

Any field can take an expression. GoRunner fills it in when the step runs. To insert one without typing, see [Use data from earlier steps](https://docs.gorunner.io/build/use-data-from-earlier-steps).

## Where data comes from

- `{{trigger.body.email}}`: The trigger’s data, such as a form’s field. `{{steps.trigger.body.email}}` is the same.
- `{{steps.step_1.id}}`: An earlier step’s output, by the step’s name.
- `{{steps.step_2.result}}`: What a Code step returned.
- `{{steps.step_3.item}}`: Inside a loop: the current item. `.index` is its place from 0, `.total` the count.
- `{{steps.step_4.resumeData}}`: What was sent to a held step’s resume link, such as an approval’s answer.
- `{{steps.step_1.error}}`: In an error step: why the step failed.
- `{{$vars.API_URL}}`: A workflow variable, plain or sensitive.
- `{{$execution.id}}`: This run’s ID. Also `$execution.mode` and `$execution.startedAt`.
- `{{$workflow.id}}`: This workflow’s ID. `{{$workspace.id}}` is its workspace’s.

**Where do I find a step’s name?**

Insert a value with the data picker, then point at it: the full path shows, such as `{{steps.step_1.id}}`.

## Paths

Dots go into an object; a number in brackets picks from a list: `{{steps.step_1.body.items[0].name}}`. A path that leads nowhere gives nothing, and the field is empty.

## What you get back

- **Only an expression**: It keeps its type. `{{steps.step_1.body}}` passes the object itself, a number stays a number.
- **Mixed with text**: Each expression becomes text: a string as it is, anything else as JSON. `Order {{trigger.body.id}} received`.

> **Warning** In a JSON body you write yourself, put an object as the whole value, unquoted: `{"order": {{trigger.body}}}`. Inside quotes, the first quote in the data breaks the JSON.

## JavaScript

Anything that isn’t a plain path runs as JavaScript, with the same data:

```js
{{ steps.step_1.body.items.length > 3 ? "big" : "small" }}
{{ $json.body.email.split("@")[1] }}
{{ $ifEmpty(trigger.body.name, "friend") }}
```

- `$json`: The trigger’s data. `$trigger` is the same, and `$steps` is `steps`.
- `$('step_1')`: A step’s output, by name.
- `$if(test, a, b)`: `a` when the test is true, else `b`.
- `$ifEmpty(value, other)`: `other` when the value is missing or empty.
- `$now`: Now, as a date. `$today` is the same.

An expression may run for 100 ms and return up to 16 MB. One that throws gives nothing. One that runs too long or uses too much memory fails the step, and says which expression.

## Numbers and dates for people

`Intl.NumberFormat`, `Intl.DateTimeFormat` and `toLocaleString` work in expressions and Code steps:

```js
{{ new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" }).format(1234.5) }}
// 1.234,50 €
```

They know about 1,100 locales and write them as Node does, with each locale’s own digits and calendar. A locale GoRunner doesn’t have falls back as in Node, to its language, then to `en-US`: `sv-XX` is written as `sv`, and `xx` as `en-US`. `en-CA` is one it has, so it gives `2026-10-26`:

```js
{{ new Intl.DateTimeFormat("en-CA", { timeZone: "Europe/Berlin", year: "numeric", month: "2-digit", day: "2-digit" }).format(new Date("2026-10-26T02:00:00Z")) }}
// 2026-10-26
```

From v2026.10.18 to v2026.10.29, only 11 locales were known, and others wrote like their language or `en-US`: `en-CA` gave `10/26/2026`.

## Test data while you build

Before anything has run, the data picker offers each trigger’s and step’s sample, so the names are real. After a test, it offers what the test returned. You can pin a step’s output so test runs reuse it; production always runs the step.

## Related

- [Use data from earlier steps](https://docs.gorunner.io/build/use-data-from-earlier-steps): The data picker, step by step · 5 min
- [Triggers](https://docs.gorunner.io/build/triggers): What each trigger puts on {{trigger.*}}
- [Variables](https://docs.gorunner.io/build/variables): Values stored on the workflow · 3 min

Source: https://docs.gorunner.io/build/expressions
