> 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/group_by_property.md).

# group\_by\_property

Groups an array by a property value in a single O(n) pass. Returns an object with `groups` (lookup by key) and `keys` (list of unique keys in order). This is faster than combining `map`, `uniq`, and multiple `where` calls.

```liquid
{% assign grouped = products | group_by_property: "vendor" %}

{% for vendor in grouped.keys %}
  <h2>{{ vendor }} ({{ grouped.groups[vendor].size }} products)</h2>
  {% for product in grouped.groups[vendor] %}
    <p>{{ product.title }}</p>
  {% endfor %}
{% endfor %}
```

#### Syntax

```liquid
{{ array | group_by_property: property }}
{{ array | group_by_property: "nested.property" }}
```

| Parameter  | Description                                                             |
| ---------- | ----------------------------------------------------------------------- |
| `array`    | Array of objects to group                                               |
| `property` | Property name to group by (supports dot notation for nested properties) |

#### Return Value

Returns an object with two properties:

| Property | Type   | Description                                                              |
| -------- | ------ | ------------------------------------------------------------------------ |
| `groups` | Object | Lookup object where keys are property values, values are arrays of items |
| `keys`   | Array  | List of unique property values in the order they were first encountered  |

#### Variants

**group\_by\_property**

Returns `{ groups: { key: [items] }, keys: [unique_keys] }` format.

```liquid
{% assign grouped = orders | group_by_property: "status" %}
{% assign pending = grouped.groups["pending"] %}
{% assign fulfilled = grouped.groups["fulfilled"] %}
```

**group\_by\_fast**

Returns `[{ name, items }, ...]` format (same as native `group_by` but faster).

```liquid
{% assign grouped = orders | group_by_fast: "status" %}
{% for group in grouped %}
  {{ group.name }}: {{ group.items.size }} orders
{% endfor %}
```

#### Examples

**Process products by material (like the import script):**

```liquid
{% comment %} Old way - O(n) + O(n) + O(n*m) {% endcomment %}
{% assign materials = products | map: "material" %}
{% assign unique_materials = materials | uniq %}
{% for material in unique_materials %}
  {% assign group = products | where: "material", material %}
  {% comment %} process group {% endcomment %}
{% endfor %}

{% comment %} New way - single O(n) pass {% endcomment %}
{% assign grouped = products | group_by_property: "material" %}
{% for material in grouped.keys %}
  {% assign group = grouped.groups[material] %}
  {% comment %} process group {% endcomment %}
{% endfor %}
```

**Group GraphQL edges by nested property:**

```liquid
{% assign edges_by_status = result.products.edges | group_by_property: "node.status" %}

{% assign active_products = edges_by_status.groups["ACTIVE"] %}
{% assign draft_products = edges_by_status.groups["DRAFT"] %}
{% assign archived_products = edges_by_status.groups["ARCHIVED"] %}

{% log "Active: " | append: active_products.size %}
{% log "Draft: " | append: draft_products.size %}
{% log "Archived: " | append: archived_products.size %}
```

**Generate report by category:**

```liquid
{% assign orders_by_region = orders | group_by_property: "shipping_address.country" %}

{% log "=== Orders by Region ===" %}
{% for country in orders_by_region.keys %}
  {% assign country_orders = orders_by_region.groups[country] %}
  {% assign total = 0 %}
  {% for order in country_orders %}
    {% assign total = total | plus: order.total_price %}
  {% endfor %}
  {% log country | append: ": " | append: country_orders.size | append: " orders, $" | append: total %}
{% endfor %}
```

**Batch process variants by product:**

```liquid
{% assign variants_by_product = all_variants | group_by_property: "product_id" %}

{% for product_id in variants_by_product.keys %}
  {% assign product_variants = variants_by_product.groups[product_id] %}

  {% comment %} Update all variants for this product in one API call {% endcomment %}
  {% json mutation_input %}
    {
      "productId": "gid://shopify/Product/{{ product_id }}",
      "variants": [
        {% for variant in product_variants %}
          { "id": "{{ variant.id }}", "price": "{{ variant.new_price }}" }{% unless forloop.last %},{% endunless %}
        {% endfor %}
      ]
    }
  {% endjson %}

  {% graphql query: bulk_update_mutation, variables: mutation_input as result %}
{% endfor %}
```

**Using group\_by\_fast for simple iteration:**

```liquid
{% assign grouped = line_items | group_by_fast: "vendor" %}

{% for group in grouped %}
  <div class="vendor-section">
    <h3>{{ group.name }}</h3>
    <ul>
      {% for item in group.items %}
        <li>{{ item.title }} - {{ item.price }}</li>
      {% endfor %}
    </ul>
  </div>
{% endfor %}
```

#### Performance Comparison

For an array of 10,000 products with 500 unique materials:

| Approach                      | Operations                                            |
| ----------------------------- | ----------------------------------------------------- |
| `map` + `uniq` + `where` loop | O(10,000) + O(10,000) + O(10,000 × 500) = \~5,020,000 |
| `group_by_property`           | O(10,000) = 10,000                                    |

**\~500x faster for this use case.**

#### Comparison with group\_by

| Feature            | `group_by`               | `group_by_property` / `group_by_fast`     |
| ------------------ | ------------------------ | ----------------------------------------- |
| Expression support | Yes (`"price > 100"`)    | No, property names only                   |
| Nested properties  | Yes (via expression)     | Yes (via dot notation)                    |
| Performance        | Slower (expression eval) | Faster (direct access)                    |
| Output format      | `[{ name, items }]`      | `{ groups, keys }` or `[{ name, items }]` |

#### Notes

* Supports nested properties using dot notation: `"node.status"`, `"variant.sku"`
* Items with `null` or `undefined` property values are skipped
* Keys are returned in the order they were first encountered
* Returns `{ groups: {}, keys: [] }` for empty or invalid input
* Use `group_by_fast` if you need the same output format as native `group_by`
* Use native `group_by` if you need expression evaluation (e.g., `"price > 100"`)
