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.
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.
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}}.
{ "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.
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.
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.