> For the complete documentation index, see [llms.txt](https://docs.datajet-app.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.datajet-app.com/liquid/filters/left_join.md).

# left\_join

Merges two arrays of objects based on matching keys, similar to a SQL LEFT JOIN. Each item in the first array is enriched with properties from the matching item in the second array. Items without a match get `null` values for the second array's properties.

```liquid
{% assign enriched = orders | left_join: customers, "customer_id = id" %}
{% for order in enriched %}
  {% log order.name | append: " - " | append: order.email %}
{% endfor %}
```

#### Syntax

```liquid
{{ array1 | left_join: array2, expression }}
```

| Parameter    | Description                                                                                        |
| ------------ | -------------------------------------------------------------------------------------------------- |
| `array1`     | Primary array (left side) — all items are preserved                                                |
| `array2`     | Secondary array (right side) — properties are merged into matching items                           |
| `expression` | Join condition in the format `"key1 = key2"` where `key1` is from array1 and `key2` is from array2 |

#### Return Value

Returns the first array with properties from the second array merged into each item. Items in the first array that have no match in the second array receive `null` for the second array's properties.

#### Examples

**Enrich orders with customer data:**

```liquid
{% assign enriched_orders = orders | left_join: customers, "customer_id = id" %}
{% for order in enriched_orders %}
  {% log order.name | append: " | Customer: " | append: order.first_name | append: " " | append: order.last_name %}
{% endfor %}
```

**Match import data with existing products by SKU:**

```liquid
{% assign matched = import_rows | left_join: shopify_variants, "sku = sku" %}
{% for row in matched %}
  {% if row.inventory_item_id %}
    {% log "Matched: " | append: row.sku %}
  {% else %}
    {% log "No match: " | append: row.sku %}
  {% endif %}
{% endfor %}
```

**Combine inventory levels with variant data:**

```liquid
{% assign combined = inventory_levels | left_join: variants, "inventory_item_id = inventory_item_id" %}
{% for item in combined %}
  {% log item.sku | append: ": " | append: item.available | append: " in stock" %}
{% endfor %}
```

**Merge CSV data with API results:**

```liquid
{% assign csv_rows = file.content | parse_csv %}
{% assign api_products = result.data.products.edges | map: "node" %}

{% assign merged = csv_rows | left_join: api_products, "handle = handle" %}
{% for row in merged %}
  {% if row.id %}
    {% log "Update: " | append: row.handle %}
  {% else %}
    {% log "Create: " | append: row.handle %}
  {% endif %}
{% endfor %}
```

#### How it works

```
Array 1 (orders):
  [{ id: 1, customer_id: 10 }, { id: 2, customer_id: 20 }, { id: 3, customer_id: 99 }]

Array 2 (customers):
  [{ id: 10, email: "a@test.com" }, { id: 20, email: "b@test.com" }]

Expression: "customer_id = id"

Result:
  [
    { id: 1, customer_id: 10, email: "a@test.com" },
    { id: 2, customer_id: 20, email: "b@test.com" },
    { id: 3, customer_id: 99, email: null }        ← no match, null values
  ]
```

#### Notes

* The expression format is `"key_from_array1 = key_from_array2"` — spaces around `=` are required
* Both inputs must be arrays — throws an error otherwise
* Modifies the first array in place — properties from matching items in the second array are merged directly
* Uses O(n + m) performance via internal Map lookup, not O(n \* m)
* Items without a match receive `null` for all properties from the second array (except the join key)
* If multiple items in the second array share the same key, the last one is used
* See also: `right_join`, `index_by`
