← All articles
DataWeave · MuleSoft · Quick Reads

Grouping Records in DataWeave with groupBy

groupBy is useful when a flat array needs to be organized into groups before the next integration step.

Typical examples include grouping records by country, account, status, business unit, or another routing key.

The important shape change is:

Array → groupBy → Object of Arrays

That output shape is worth understanding because groupBy behaves differently from functions such as map and filter.

Basic Example

Suppose the payload contains orders from multiple countries:

[
  { "orderId": "O1", "country": "US", "total": 100 },
  { "orderId": "O2", "country": "CA", "total": 200 },
  { "orderId": "O3", "country": "US", "total": 300 }
]

Group them by country:

%dw 2.0
output application/json
---
payload groupBy (order) -> order.country

The result is conceptually:

{
  "US": [
    { "orderId": "O1", "country": "US", "total": 100 },
    { "orderId": "O3", "country": "US", "total": 300 }
  ],
  "CA": [
    { "orderId": "O2", "country": "CA", "total": 200 }
  ]
}

Each distinct grouping value becomes a key whose value contains the matching records.

Grouping Salesforce Records

Suppose a collection of Salesforce records contains AccountId:

[
  { "Id": "C1", "AccountId": "A1", "Name": "Ravi" },
  { "Id": "C2", "AccountId": "A2", "Name": "Maria" },
  { "Id": "C3", "AccountId": "A1", "Name": "James" }
]

Group contacts by account:

payload groupBy (contact) -> contact.AccountId

This can be useful when downstream processing happens once per parent account rather than once per individual contact.

Handling Missing Grouping Keys

If the grouping field can be null or missing, decide what that means explicitly.

For example:

payload groupBy (record) -> record.region default "UNASSIGNED"

Now records without a region are placed in a known group.

Whether that is correct depends on the integration. In another design, missing region may be a validation error and should not be grouped at all.

Grouping Before Aggregation

groupBy is often the first step in an aggregation.

Suppose you want order totals by country. First group the records, then transform each group:

%dw 2.0
output application/json
---
(payload groupBy (order) -> order.country)
    mapObject (orders, country) -> {
        (country): {
            orderCount: sizeOf(orders),
            totalAmount: sum(orders.total)
        }
    }

For the earlier example, the output is:

{
  "US": {
    "orderCount": 2,
    "totalAmount": 400
  },
  "CA": {
    "orderCount": 1,
    "totalAmount": 200
  }
}

Notice the combination:

groupBy   → creates an object of grouped arrays
mapObject → transforms those groups

This is a good example of choosing DataWeave functions based on data shape.

Converting Groups Back into an Array

A downstream API may expect an array rather than a dynamic-key object.

You can transform the grouped object into an array of group summaries:

%dw 2.0
output application/json
---
(payload groupBy (order) -> order.country)
    pluck (orders, country) -> {
        country: country,
        orderCount: sizeOf(orders),
        orders: orders
    }

Conceptual output:

[
  {
    "country": "US",
    "orderCount": 2,
    "orders": [ ... ]
  },
  {
    "country": "CA",
    "orderCount": 1,
    "orders": [ ... ]
  }
]

pluck is useful here because it turns object entries into array items while giving access to both each group value and its key.

Grouping for Routing or Batching

Grouping is not only for reporting.

Imagine records must be sent to different downstream endpoints based on business unit:

payload groupBy (record) -> record.businessUnit

The groups can then become units of subsequent processing.

Similarly, records might be grouped by a parent identifier before constructing one request per parent.

However, do not confuse logical grouping with transport-level batch sizing. A group containing 50,000 records may still need to be divided into smaller chunks before calling a downstream system.

Grouping by a Derived Value

The grouping key does not have to come directly from a field.

For example:

payload groupBy (order) ->
    if ((order.total default 0) >= 1000)
        "HIGH_VALUE"
    else
        "STANDARD"

This produces business-defined groups based on a rule rather than a source attribute.

If that rule is reused or complex, move it into a named function for readability.

Normalize Keys Before Grouping When Appropriate

Source data sometimes contains inconsistent values such as:

US
us
Us

If those values are intended to represent one group, normalize first:

payload groupBy (record) -> upper(record.country default "UNKNOWN")

But normalization should reflect a known business rule. Do not merge distinct source values merely because they look similar.

groupBy vs filter

These functions answer different questions.

filter asks:

Which records should remain?

groupBy asks:

How should the remaining records be organized?

For example:

(payload filter (order) -> order.status == "READY")
    groupBy (order) -> order.region

This first selects eligible orders and then organizes them by region.

Quick Decision Guide

RequirementTypical function
Keep selected recordsfilter
Transform recordsmap
Organize records by a keygroupBy
Transform grouped object entriesmapObject
Turn grouped object entries into an arraypluck

Practical Rule

Use groupBy when the next processing step naturally thinks in terms of collections sharing the same key.

Then pay attention to the resulting shape: you no longer have a simple array. You have an object whose values are arrays, and the next DataWeave function should be chosen accordingly.