# Group

Group records by key paths and compute counts, sums, lists and other aggregations per group.

## When to use it

Group by one or more field paths and define the aggregations for each group. Use descriptive output names so a later table or chart can distinguish the group key, counts, sums, and collected values.

## Add and configure

1. Add **Group** 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 `group`.

## Settings

| Setting | Accepted value | Required | Default | What it controls |
| --- | --- | --- | --- | --- |
| Aggregations (`aggregations`) | list of object | No | Not specified | Output fields computed per group, e.g. `{"op": "count", "as": "n"}`. |
| As (`aggregations[].as`) | string; minimum characters 1 | Yes | Not specified | Output field for the result. |
| Op (`aggregations[].op`) | "count" / "count_distinct" / "sum" / "avg" / "min" / "max" / "concat" / "list" / "first" / "last" | Yes | Not specified | The reduction. |
| Path (`aggregations[].path`) | string or null | No | null | JSONPath of the value to reduce (first match per record). Omitted: `count` counts records, `count_distinct` distinct records, `list`/`first`/`last` take whole records. |
| Separator (`aggregations[].separator`) | string | No | "\n" | Separator for `concat`. |
| where (`aggregations[].where`) | object or null | No | null | Only the records matching this predicate feed the aggregation. |
| Op (`aggregations[].where.op`) | "eq" / "ne" / "contains" / "exists" / "in" | No | "exists" | Comparison operation (strings compare case-insensitively). |
| Path (`aggregations[].where.path`) | string | Yes | Not specified | JSONPath into each record. |
| Value (`aggregations[].where.value`) | value or null | No | null | Comparison value (ignored for 'exists'). |
| Keys (`keys`) | list of string or object | No | Not specified | Grouping keys, each a JSONPath (written to the output under its last field name) or `{path, as, fallback, emit}`. Empty: one group over every record. |

## Inputs and outputs

| Direction | Port | Content | Required | Multiple records |
| --- | --- | --- | --- | --- |
| Inputs | `in` | 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: Count and total the points per team

Step settings:

```json
{
  "aggregations": [
    {
      "as": "tickets",
      "op": "count"
    },
    {
      "as": "points",
      "op": "sum",
      "path": "$.points"
    }
  ],
  "keys": [
    "$.team"
  ]
}
```

Input records:

```json
[
  {
    "points": 3,
    "team": "core"
  },
  {
    "points": 5,
    "team": "web"
  },
  {
    "points": 2,
    "team": "core"
  }
]
```

Expected output for this example:

```json
[
  {
    "points": 5.0,
    "team": "core",
    "tickets": 2
  },
  {
    "points": 5.0,
    "team": "web",
    "tickets": 1
  }
]
```

## Example: Join each document's segments into one text

Step settings:

```json
{
  "aggregations": [
    {
      "as": "object_key",
      "op": "first",
      "path": "$.object_key"
    },
    {
      "as": "text",
      "op": "concat",
      "path": "$.text",
      "separator": "\n"
    }
  ],
  "keys": [
    {
      "emit": false,
      "fallback": [
        "$.object_key"
      ],
      "path": "$.document_id"
    }
  ]
}
```

Input records:

```json
[
  {
    "document_id": "d1",
    "object_key": "a.pdf",
    "text": "Page one"
  },
  {
    "document_id": "d1",
    "object_key": "a.pdf",
    "text": "Page two"
  },
  {
    "object_key": "b.pdf",
    "text": "Only page"
  }
]
```

Expected output for this example:

```json
[
  {
    "object_key": "a.pdf",
    "text": "Page one\nPage two"
  },
  {
    "object_key": "b.pdf",
    "text": "Only page"
  }
]
```

## Example: Census of the whole input with conditional counts

Step settings:

```json
{
  "aggregations": [
    {
      "as": "seen",
      "op": "count"
    },
    {
      "as": "flagged",
      "op": "count",
      "where": {
        "op": "eq",
        "path": "$.is_ad",
        "value": true
      }
    }
  ]
}
```

Input records:

```json
[
  {
    "is_ad": true
  },
  {
    "is_ad": false
  },
  {
    "is_ad": true
  }
]
```

Expected output for this example:

```json
[
  {
    "flagged": 2,
    "seen": 3
  }
]
```

## 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.
