# Persisting data

> Map an incoming payload onto a data model field by field, save the result to the warehouse, and keep every version of every record.

**Map to Data Model** is the step that turns a payload into a record. Whatever
the steps above it produced goes in; a row shaped like one of your data models
comes out and is saved to the warehouse.

It is the only step type that writes to the warehouse. Everything else in a
workflow moves data around; this is where data lands and stays.

## Add the step

In the builder, open the step palette and pick **Map to Data Model**. Its node
sits on the canvas reading **Select data model…** until you choose one.

Select the node and the inspector shows a **Data Model** picker, a
**Field Mappings** divider with an **{n} of {n} mapped** counter, and a button
labelled **Map to** *your model's name*. That button opens the mapping panel,
which is where the work happens. The node itself carries the same counter as a
badge, so you can see at a glance which mapping steps are unfinished.

## The mapping panel

Two columns. **Available fields** on the left is everything upstream: one group
per source, each field shown as `source.field` with its type and whether it is
nullable. **Destination** on the right is your data model's fields, each showing
its type, a **PK** badge on the primary key, and a red asterisk on anything
required.

Mapping is click-then-click, not drag-and-drop. Click a field on the left to arm
it, then click the destination row you want it to fill. Click the armed item
again to disarm it. Hovering a field traces its connection across the middle
lane, which is how you check an existing mapping without opening anything.

An unmapped destination row says **unmapped** in italics and draws a dashed
border. While something is armed the same row says **click to connect**.

Each mapped row gets a small badge naming where its value comes from: the source
slug for a plain field reference, **static** for a literal you typed, **var** for
a computed value, and **fx** for anything else.

The search box filters every upstream field at once, and the chips above it
narrow to one source, or to **Vars**.

When nothing upstream has been sampled yet, the left column is empty and says
so — sample a Kafka trigger from its topic, or test an HTTP step, and the fields
appear. See [Triggers and webhooks](/help/workflows/triggers-and-webhooks).

## What a mapping can be

A mapping is one destination field plus one expression. Three shapes cover
almost everything:

- **A field reference.** `orders.total` copies a value across unchanged. This is
  what clicking a left-column field produces.
- **A static value.** `'shopify'` or `0` or `true` writes a constant on every
  record. Useful for tagging where a record came from.
- **A formula.** Anything computed: `input.price * 1.2`,
  `input.first_name + ' ' + input.last_name`,
  `ROUND(input.price * 1.0825, 2)`.

Every required field has to be mapped, and so does the primary key, before the
workflow can be activated.

## Computed values

A formula you need twice should not be written twice. The left column has a
**Formulas** group at the top, and **+ New formula** opens the formula builder.

A formula has a name, a value type — `string`, `integer`, `float`, `decimal`,
`boolean`, `datetime`, `date`, `map`, or `array` — and an expression. Once
saved, reference it from any destination field as `vars.your_name`, as many times
as you like. The list shows a use count beside each one, and renaming a formula
repoints every field that referenced it.

Formulas can read upstream fields and other formulas. A reference cycle between
two formulas is rejected.

The builder gives you two picklists to compose from. **Fields** lists your
upstream fields grouped by source, plus your other formulas. **Operators** is
grouped into **Functions**, **Logical**, **Comparison**, and **Mathematical**:

| Group | Available |
|---|---|
| Functions | `IF` if / then / else · `COALESCE` first non-empty · `ROUND` round a number · `LENGTH` list / string length · `EVERY` all items match · `SOME` any item matches · `AT` element at index · `FIRST` first element · `LAST` last element |
| Logical | `AND` all true · `OR` any true · `NOT` negate · `\|\|` fallback / or-else |
| Comparison | `==` · `!=` · `>` · `<` · `>=` · `<=` |
| Mathematical | `+` add / concatenate · `-` subtract · `*` multiply · `/` divide |

**Validate** checks the expression against the real engine rather than a
client-side approximation, and **Save formula** stays disabled until it passes.
Errors are specific: an empty formula, an invalid expression, a name that is
already taken, or a name that breaks the letters-numbers-underscore rule.

## Validation and drift

The step itself refuses to be valid without a data model, without at least one
mapping, and without the model's primary key mapped. The canvas surfaces those
as **Select a data model**, **No field mappings configured**, and
**Primary key field must be mapped**.

Separately, sources go stale. When a source's configuration changes after it was
last sampled, a banner appears: *A source's configuration changed since it was
last sampled. Existing mappings still save, but this workflow can't be activated
until you re-sample.* Each affected source gets its own
**Re-sample** button. Mappings keep saving throughout — it is only activation
that is gated, so you never lose work to drift.

## What the warehouse keeps

Each run of the step saves one record: the mapped values, the data model they
belong to, and a pointer back to the workflow version and step that produced
them. Three properties of that store are worth knowing, because the rest of the
product is built on them.

**Nothing is ever overwritten.** A second write for the same primary key does
not replace the first — it becomes the next entry in that entity's history. Open
**Warehouse**, pick a data model, and click an entity to get its **Change
history**: every version that was ever saved, with a date picker to scrub back
and see the entity as it stood at an earlier moment. The timeline is complete by
construction, because nothing is updated and nothing is deleted.

**Every record knows where it came from.** Because each one carries the workflow
version and step that produced it, "where did this number come from?" is a
question with an answer rather than an investigation. **Workflows → Monitor**
holds the other half of that trail: what each step of each run did, recorded as
it happened, with the payload it was holding at the time. That same running
commentary is how an interrupted run picks up where it left off instead of
repeating work that already succeeded.

**Dashboards read these records directly.** A widget names a data model, a
field, an aggregation, and a time range, and Hodoflow computes it — no query
language involved. Records are time-stamped with when the thing happened, so
time is a first-class filter rather than an afterthought: the same widget can
show the last hour, yesterday, or the year to date with nothing recomputed
upstream. And because each new record is broadcast as it is saved, widgets
update on screen as data lands instead of waiting for a refresh.

Because every source is mapped onto the same data models, data from three
different systems ends up genuinely comparable. Defining the shape once is what
does that.

The whole path, in one line each:

1. Something arrives from outside, or a workflow goes and fetches it.
2. A trigger starts a run.
3. Steps fetch, transform, and branch, each outcome recorded as it happens.
4. **Map to Data Model** maps the result onto a data model.
5. A record is saved, and that entity's history grows by one version.
6. Every dashboard watching that data model updates.

## Where to go next

- [Data models](/help/data-dashboards/data-models) — defining the shape you are mapping
  onto.
- [Dashboards](/help/data-dashboards/dashboards) — reading records back out.
- [Workflows](/help/workflows) — the steps that run before this one, and
  the run history behind every record.
