# Use fields from an earlier step

> Reference an earlier step's output in a URL, header, body, or condition, and understand the names those fields are known by.

Steps below another one can read what it produced. The reference is always the
step's **API Name**, then the path to the value inside its output.

## Outcome

A step uses values that came from an earlier step, rather than hard-coded ones.

## Before you start

The earlier step must have been tested or sampled, or there is nothing for
Hodoflow to offer you. See [Configure and test a
step](/help/workflows/configure-and-test-steps).

## 1. Know what a field is called

Every step has an **API Name** at the top of its inspector — a short identifier
made of letters, numbers, and underscores, unique within the workflow. That is
how everything downstream refers to it.

If a step named `Fetch rate` has the API Name `fx`, and its response was
`{"base": "USD", "rates": {"EUR": 0.86}}`, then the fields available below it
are:

```
fx.base
fx.rates.EUR
```

Nested values are reached with dots. The trigger's own payload is available as
`input` — and `trigger` is accepted as a synonym for the same thing.

## One record, or a list of them

Every step hands down one of two things, and it decides how you name what is
inside it. This is the rule the whole builder follows:

| What the step hands down | How you reference it |
| --- | --- |
| **One record** | `fx.base` — the field, straight off the step |
| **A list of records** | `orders.data` for the whole list; `item.total` for each record, inside a step that runs once per item |

A step that hands down a list carries a **list** badge on its card, and a step
running once per item carries a **per item** badge — so you can see where the
flow widens without opening anything.

`item` only exists inside a step that is running once per item. Set that up in
the step's **Iteration** section: **Mode → Run once per item**, then pick the
list under **For each item in**. When you click a per-record field in the
**Available fields** list of a step that is not doing that yet, Hodoflow asks
whether it should — say yes and it is set up for you, with the reference
inserted as `item.<field>`.

Reading `orders.total` off a list-shaped step resolves to nothing, so
activation refuses it and names the fix. See [Activation
errors](/help/workflows/activation-errors).

## 2. Insert it

In an **HTTP Request** or **API Request** step, values go in as `{{field}}`
template variables. They work in the **URL**, in any **Query Param** or
**Header** value, and in the **Body**.

An **Available fields** panel sits above the **Test Request** button, listing
every upstream field as a clickable chip. Click into the input you want first,
then click a chip: the template is inserted at the cursor rather than appended
to the end.

```
https://api.example.com/orders/{{input.order_id}}
```

Anything you type that is not a `{{field}}` is sent exactly as written, so a
body holding `"usd"` sends `usd`.

In a **JSON** body, a value that is *only* a `{{field}}` keeps the field's own
type — a number stays a number rather than becoming text — anywhere in the
document, including inside a list. Mix it with anything else and the result is
text, which is what you want for something like `order-{{input.order_id}}`.

```json
{ "amount": "{{input.order.total}}", "currency": "usd" }
```

sends `{"amount": 4200, "currency": "usd"}`.

In a **Conditional** step, branch conditions are formulas over the same names —
`input.total > 100`. In a **Filter List** step, the condition is evaluated per
item, where the current element is `item`.

**Checkpoint:** the field appears inside double braces in the input, and the
chip list still shows it as available.

## Fields about the run itself

Not everything a step needs comes from the step above it. `flow` is available
in every step of every workflow and describes the run it is part of:

| Field | What it holds |
| --- | --- |
| `flow.name` | The workflow's name |
| `flow.id` | The workflow's id, which stays the same across every version you save |
| `flow.run_id` | This run's id — useful as a correlation or idempotency value on an outgoing request |
| `flow.workspace` | The workspace's name |
| `flow.started_at` | When this run started |
| `flow.last_run_at` | When the previous run of this workflow started, whatever happened to it |
| `flow.last_successful_run_at` | When the last run that **succeeded** started |
| `flow.last_run_status` | `succeeded`, `failed`, or whatever state the previous run is in |

The timestamps are written as `2026-09-13T02:00:00Z`.

This is what lets a step ask for only what has changed:

```
https://api.example.com/orders?updated_since={{flow.last_successful_run_at}}
```

Two things about that example are worth knowing before you rely on it.

**The watermark is when the last successful run *started*, not when it
finished.** A run that starts at 02:00 and finishes at 02:05 may or may not have
seen records created during those five minutes. Using the start time means the
next run reads that window again — a few duplicates, which a data model's
identity absorbs — instead of skipping it forever.

**On the first run there is no previous run, so the value is empty.** The
example above sends `?updated_since=` that first time, which most APIs read as
no filter at all and answer with everything. That is usually what you want — a
full first load, incremental after that — but if it is not, a **computed value**
can state a floor for that first run. See below.

Every value is fixed when the run starts and stays fixed for the whole run, so a
run that pauses on a **Delay** for a week resumes with the same values it began
with, and two runs of the same workflow never read each other's timestamps.

Inside a **subflow**, `flow` describes the subflow's own run, not the workflow
that called it. A subflow reads what its caller passed in under `params`
instead — see [Step types](/help/workflows/step-types).

## Values a step works out for itself

A `{{reference}}` inserts a value exactly as it is. It does no arithmetic, so
when an API wants the watermark in some *other* shape, a reference alone cannot
get you there. Plenty of them do: a filter that takes a unix timestamp, a date
with no time, or an id that has a time encoded in it.

Every step has a **Computed values** section in its inspector for that. Give a
value a name and a formula, and read it back anywhere that step takes a value —
a URL, a query parameter, a header, a body field:

| Function | What it does |
| --- | --- |
| `NOW()` | The current time |
| `DATE_ADD(date, amount, unit)` | Shifts a date. A negative amount goes back. Units: second, minute, hour, day, week, month, year |
| `DATE_DIFF(later, earlier, unit)` | Whole units between two dates — second through week |
| `EPOCH(date)` | Unix time in seconds |
| `EPOCH_MS(date)` | Unix time in milliseconds |
| `FORMAT_DATE(date, pattern)` | Renders a date — `'%Y-%m-%d'` gives `2026-09-13` |
| `PARSE_DATE(value)` | Reads a date from text or from a unix timestamp |

So an API that filters on unix seconds gets a computed value named `since`:

```
EPOCH(flow.last_successful_run_at)
```

and the step's parameter reads `{{vars.since}}`.

**Give the first run a floor with `COALESCE`.** With no previous run the
watermark is empty, and every function above answers an empty date with an
empty value. `COALESCE` takes the first value that is actually there, so this
reads the watermark when there is one and thirty days back when there is not:

```
EPOCH(COALESCE(flow.last_successful_run_at, DATE_ADD(NOW(), -30, 'day')))
```

**A computed value can build on another one.** They are resolved in whatever
order their dependencies require, so one may read another as `vars.<name>`. That
is what makes a filter like Discord's reachable — its message list takes an id
rather than a time, and an id is derived from one:

```
ms         = EPOCH_MS(COALESCE(flow.last_successful_run_at, DATE_ADD(NOW(), -30, 'day')))
message_id = (vars.ms - 1420070400000) * 4194304
```

Not every API has a way to ask, whatever you can compute. Discord, to stay with
it, has no "edited since" filter in any form — so a message edited after you
last read it cannot be found by asking, only by reading the window again.

Renaming a computed value updates the places that read it, within that step.

## 3. Renaming, and what it costs

Changing a step's **API Name** does not update the references that point at it.
Anything using the old name silently stops resolving, which usually shows up as
an empty value rather than an error.

Set the API Name when you add the step. If you must change it later, search the
workflow for the old name first.

## Mapping into a data model

Putting values into a warehouse record is a different job with its own panel:
select a **Map to Data Model** step and press the **Map to** button. That panel
is click-then-click, supports reusable formulas, and is covered in [Persist
workflow data](/help/data-dashboards/persisting-data).

## Reshaping without saving a record

A **Data Transform** step uses the same panel to build a shape of its own rather
than a data model's: select the step and press **Edit mappings**. The left pane
is the same searchable index of upstream fields and reusable formulas; the right
pane starts empty, and **+ Add field** adds an output field you name yourself.
Click a source field on the left, then the output field on the right, to connect
them.

Whatever the panel lists becomes the step's output, so the step below reads
`<api name>.<field>` for each one. A step with no mapped field stays incomplete
and refuses activation with *add at least one field mapping*.

## Common problems

**The chips list is empty.** Nothing upstream has been tested. The inspector
says *No upstream schema yet. Test an upstream HTTP node to discover its
fields.*

**A template resolved to nothing.** The path is wrong, or the API Name changed.
Check the exact path in the **Available fields** list — `fx.rates.EUR` is not
`fx.EUR`.

**`API Name must be unique`.** Two steps in this workflow have the same API
Name. Rename one.

**The API Name box rejects what I typed.** Only letters, numbers, and
underscores are accepted. No spaces, no hyphens.

## Next step

[Save, version, and activate](/help/workflows/save-version-activate).
