# Merge

Combine the records on two named input ports: union, left/inner join or coalesce.

## When to use it

Connect both named input ports and choose union, join, or coalescing behaviour. Join keys determine which rows match. A left join keeps unmatched left-side rows; an inner join keeps only matches. Duplicate keys can change the number of output rows.

## Add and configure

1. Add **Merge** from the workflow step picker.
2. Give the step a recognisable name and connect its required inputs.
3. Set the options below. Nested names identify fields within an object or a list item.
4. Validate the recipe, inspect the test result, then run it on the intended data.

The step identifier used in the editor’s code view is `merge`.

## Settings

| Setting | Accepted value | Required | Default | What it controls |
| --- | --- | --- | --- | --- |
| Keys (`keys`) | list of string | No | Not specified | Key paths read on each left record — and on each right record unless `right_keys` is set (e.g. `["$.customer_id"]`). Required by the joins and `coalesce`; `union` takes none. |
| Max Output Records (`max_output_records`) | integer; minimum 1; maximum 500000 | No | 100000 | The step fails (`output_cap`) rather than emit more records than this — duplicate keys multiply join rows. |
| Mode (`mode`) | "union" / "left_join" / "inner_join" / "coalesce" | No | "union" | How the two ports combine: `union` concatenates left then right; `left_join` / `inner_join` match records on the key paths; `coalesce` keeps one record per key, each field taking the first non-null value. |
| Prefer (`prefer`) | "left" / "right" | No | "left" | The side whose value wins a field both records carry (joins), or whose non-null value is taken first (`coalesce`). |
| Right Into (`right_into`) | string; minimum characters 1 or null | No | null | Joins only: write the matched right record under this field instead of merging its fields into the left record (`null` when a `left_join` finds none). |
| Right Keys (`right_keys`) | list of string or null | No | null | Key paths read on each right record, one per entry of `keys`, when the right side names its key differently (e.g. `["$.id"]`). |

## Inputs and outputs

| Direction | Port | Content | Required | Multiple records |
| --- | --- | --- | --- | --- |
| Inputs | `left` | records | Yes | Yes |
| Inputs | `right` | records | Yes | Yes |
| Outputs | `out` | records | Yes | Yes |

## Effects, testing, and recovery

Transforms or passes records without a declared external side effect.

The declared preview behaviour is to execute this step. Reads, processing, or model usage can therefore be real. Read [dry runs](/docs/workflows/dry-runs) before using a preview with live connections.

No duplicate-prevention key is declared for this step. The step declares safe replay for its supported recovery path.

## Example: Enrich orders with their customer (left join)

Step settings:

```json
{
  "keys": [
    "$.customer_id"
  ],
  "mode": "left_join",
  "right_into": "customer",
  "right_keys": [
    "$.id"
  ]
}
```

Expected output for this example:

```json
[
  {
    "customer": {
      "id": 1,
      "name": "Ada"
    },
    "customer_id": 1,
    "order": "o1"
  },
  {
    "customer": null,
    "customer_id": 9,
    "order": "o2"
  }
]
```

## Example: Keep only the tickets that have an owner (inner join)

Step settings:

```json
{
  "keys": [
    "$.owner"
  ],
  "mode": "inner_join",
  "right_keys": [
    "$.login"
  ]
}
```

Expected output for this example:

```json
[
  {
    "login": "ada",
    "owner": "ada",
    "team": "core",
    "ticket": 1
  }
]
```

## Example: Concatenate two sources (union)

Step settings:

```json
{
  "mode": "union"
}
```

Expected output for this example:

```json
[
  {
    "title": "from web"
  },
  {
    "title": "from rss"
  }
]
```

## Example: Fill the gaps of one profile source from another (coalesce)

Step settings:

```json
{
  "keys": [
    "$.email"
  ],
  "mode": "coalesce"
}
```

Expected output for this example:

```json
[
  {
    "email": "ada@example.com",
    "name": "Ada",
    "phone": "555-0100"
  },
  {
    "email": "grace@example.com",
    "name": "Grace"
  }
]
```

## Check the result

Inspect the selected run’s status, counts, errors, and output records. An empty collection can be a valid result, and a completed step can contain per-record failures where the step defines that behaviour. Check the next step’s expected record shape before publishing or delivering the result.
