# Write modes and history

What the history layer remembers, how records are meant to land in staging, and the keys both depend on.

## Overview

Two settings in the project file describe what your data platform ends up holding, and they are easy to mistake for one setting. `writeMode` covers how records land in staging. `historyMode` decides what the history layer keeps once they have. They are independent of each other, and they are not equally live today, which the next section explains.

Together with the load strategy, they are three separate decisions:

| Decision | Setting | Question it answers |
| --- | --- | --- |
| How much you read | Load strategy | Everything, or only what changed? (full loads today; incremental is on the roadmap) |
| How records land | `writeMode` | Add to staging, merge into it, or overwrite it? (not yet applied) |
| What is kept | `historyMode` | Does the history layer remember yesterday? |

## Write modes

`writeMode` describes how a load is meant to treat what is already in the staging table.

| Mode | What it describes |
| --- | --- |
| `append` | Adds new records, leaving existing ones alone |
| `upsert` | Merges records, updating those that match on the key |
| `replace` | Overwrites everything already there |

It is set on a job, or on the defaults of an object selection. It is **not** overridable per object. If one table in a selection needs a different write mode, it needs its own job.

### Not yet applied at run time

Every run currently rewrites the staging table in full, whatever `writeMode` says: staging is truncated and reloaded on each execution, and the configured value is not read by the pipeline. Set it to record your intent if you like, but do not rely on it to change what a run does. Honoring it is on the roadmap.

The gap is between the project file and the run, not a missing capability underneath: all three behaviors are implemented in the connectors that do the writing.

## History modes

`historyMode` governs what the history layer (the layer a job names `history`) keeps as the source changes underneath it. Unlike `writeMode`, it **is** overridable per object, because the right answer genuinely differs table by table: a customer dimension usually wants full history, and a lookup table of country codes usually does not.

The common choices are full SCD Type 2 history, where each change writes a new version of the record with its own validity window; SCD Type 1, which overwrites in place and keeps only the current value; snapshot, which builds versioned, change-only history from a full extract: each run closes the versions of records that changed or disappeared and writes only the changes; and append, which adds records without touching what is there. Several further modes cover narrower cases: deduplicating appends, event-time-stamped history, and full rebuilds.

The [project file reference](/docs/reference/project/) carries the authoritative list of modes and is generated from the shipping schema, so it never disagrees with the version you are running. Your connector's page in the [integrations catalog](/integrations/) shows which of them that target actually supports.

## Why history is the expensive decision to change

Changing `historyMode` changes what you are able to ask later, and it cannot fill in the past retroactively.

A table loaded with SCD Type 1 for six months can be switched to SCD Type 2 tomorrow, and history will begin accumulating tomorrow. The six months are gone: not deleted, never captured. That asymmetry is the reason to decide history deliberately at the start rather than when someone first asks what a value used to be.

## Keys decide whether history works

Historization depends on knowing which records are the same record. That comes from the keys in the data contract, and it is the most common thing to get wrong.

A **business key** identifies a record in the source's own terms, such as an order number or a customer code. Historization is built on it: to know that an incoming record is a new version of an existing one rather than a new record, the pipeline has to be able to match them.

The failure mode here is quiet. A contract with a missing or wrong primary key need not fail the run. With no key declared, the run keys each record by all of its properties and loads history only when those records prove unique, so a changed value is recorded as one record ending and an unrelated one beginning, not as a new version of the same record. The history looks complete and answers the wrong question. The symptom shows up weeks later, when someone asks how a record changed and its history has no versions to show.

Check keys during [contract review](/docs/guides/sources/review-the-data-contract/), before any data moves. Correcting a key afterwards means rebuilding the history that was accumulated under the wrong one.

## Next

- [Choose a load strategy](/docs/guides/jobs/choose-a-load-strategy/): the third decision, full versus incremental
- [Layers and naming](/docs/guides/targets/layers-and-naming/): where staging, history, and bridge layers sit
- [Type resolution](/docs/concepts/type-resolution/): how the columns got their types
