---
title: "Row Grouping - Sorting"
enterprise: true
framework: javascript
version: "36.1.0"
---

# Row Grouping - Sorting

This section provides details on how to configure and customise how row groups are sorted.

## Sorting Row Groups

Row Groups are [Sorted](https://www.ag-grid.com/javascript-data-grid/row-sorting/) by the column that they are grouped by, and use any [Custom Sorting](https://www.ag-grid.com/javascript-data-grid/row-sorting/#custom-sorting) configured on that column. Applying a sort to a group column generated by `groupDisplayType` will apply the sort to the row grouped columns it represents.

#### Mixed Group Sort

```ts
import {
  ClientSideRowModelModule,
  GridApi,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
} from "ag-grid-enterprise";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
]);

let gridApi: GridApi;

const gridOptions: GridOptions = {
  columnDefs: [
    { field: "country", rowGroup: true, sort: "desc" },
    { field: "year", rowGroup: true, sort: "asc" },
    { field: "athlete" },
    { field: "total" },
  ],
  defaultColDef: {
    flex: 1,
    minWidth: 100,
  },
  autoGroupColumnDef: {
    minWidth: 300,
  },
  groupDefaultExpanded: 1,
};

const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);

fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
  .then((response) => response.json())
  .then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
```

[Live example: Mixed Group Sort](https://www.ag-grid.com/examples/grouping-sorting/mixed-group-sort/typescript)

The example above demonstrates that sorting the `country` and `year` columns will sort the row groups, and clicking to sort the `Group` column applies sorting to the `country` and `year` columns.

> **Note**
>
> When using `groupDisplayType` with a [Single Group Column](https://www.ag-grid.com/javascript-data-grid/grouping-single-group-column/) and the columns with row grouping applied have different sort directions, the group column will instead display the mixed sort icon.

```js
const gridOptions = {
    columnDefs: [
        { field: 'country', rowGroup: true, sort: 'desc' },
        { field: 'year', rowGroup: true, sort: 'asc' },
        // ...other column definitions
    ],
    groupDisplayType: 'singleColumn',

    // other grid options ...
}
```

## Custom Row Group Sorting

The generated Group Columns can be unlinked from the columns with row grouping by configuring [Custom Group Sorting](https://www.ag-grid.com/javascript-data-grid/row-sorting/#custom-sorting) using a `autoGroupColumnDef.comparator`. This allows custom sorting to be applied across all levels of row grouping.

The example below demonstrates a configuration that ignores the data entirely, sorting rows by the number of descendants instead:

```js
const gridOptions = {
    columnDefs: [
        { field: 'country', rowGroup: true },
        { field: 'year', rowGroup: true },
        // ...other column definitions
    ],
    autoGroupColumnDef: {
        comparator: (valueA, valueB, nodeA, nodeB) => {
            return nodeA.allLeafChildren.length - nodeB.allLeafChildren.length;
        },
    },

    // other grid options ...
}
```

#### Custom Group Sort

```ts
import {
  ClientSideRowModelModule,
  GridApi,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
} from "ag-grid-enterprise";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
]);

let gridApi: GridApi;

const gridOptions: GridOptions = {
  columnDefs: [
    { field: "country", rowGroup: true },
    { field: "year", rowGroup: true },
    { field: "athlete" },
    { field: "total" },
  ],
  defaultColDef: {
    flex: 1,
    minWidth: 100,
  },
  autoGroupColumnDef: {
    minWidth: 300,
    comparator: (valueA, valueB, nodeA, nodeB) =>
      (nodeA.allLeafChildren?.length ?? 0) -
      (nodeB.allLeafChildren?.length ?? 0),
  },
  groupDefaultExpanded: 1,
};

const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);

fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
  .then((response) => response.json())
  .then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
```

[Live example: Custom Group Sort](https://www.ag-grid.com/examples/grouping-sorting/custom-group-sort/typescript)

> **Note**
>
> When using custom group sorting, sorting the `Group` column no longer impacts the columns with row grouping, and vice versa.

## Maintain Group Order

By default, sorting on a non-group column reorders groups based on the sort. To keep groups in their structural order while only sorting the rows within each group, enable `groupMaintainOrder`:

```js
const gridOptions = {
    columnDefs: [
        { field: 'country', rowGroup: true, hide: true },
        { field: 'athlete' },
    ],
    groupMaintainOrder: true,

    // other grid options ...
}
```

With `groupMaintainOrder=true`:

- Sorting a leaf column sorts the rows inside each group; groups stay in structural order.
- Filter changes preserve group order.
- Transactions add new groups at their structural position: the end of the data-insertion order, or the position produced by `initialGroupOrderComparator` if one is configured.
- With multi-level row grouping, the order is maintained per level. Sorting a group column at one level only re-orders that level's groups; sibling levels keep their structural order. For example, with `country` and `year` grouping, sorting `year` re-orders year groups within each country, while country groups remain in their structural slot.

#### Maintain Group Order

```ts
import {
  ClientSideRowModelModule,
  GridApi,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
} from "ag-grid-enterprise";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
]);

let gridApi: GridApi;

const gridOptions: GridOptions = {
  columnDefs: [
    { field: "country", rowGroup: true, hide: true },
    { field: "year", rowGroup: true, hide: true },
    { field: "athlete" },
    { field: "total" },
  ],
  defaultColDef: {
    flex: 1,
    minWidth: 100,
  },
  autoGroupColumnDef: {
    minWidth: 200,
  },
  // Display each group level as its own column so each can be sorted independently.
  groupDisplayType: "multipleColumns",
  // Groups stay in structural (data-insertion) order. Sorting a leaf column reorders leaves
  // inside their year group only; sorting a group column reorders that level only.
  groupMaintainOrder: true,
  groupDefaultExpanded: 1,
};

const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);

fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
  .then((response) => response.json())
  .then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
```

[Live example: Maintain Group Order](https://www.ag-grid.com/examples/grouping-sorting/maintain-group-order/typescript)

The example above uses `groupDisplayType: 'multipleColumns'` so each group level has its own header. Try the following to see per-level isolation:

- Sort `Athlete` or `Total` (leaf columns): only the rows inside each year group reorder; year and country groups keep their structural order.
- Sort the `Year` group column: only year groups within each country reorder; country groups stay structural.
- Sort the `Country` group column: only country groups reorder; year groups within each country keep their structural order.
- Clear a group-column sort: that level reverts to structural order; sibling levels are unaffected.

The structural order is the data-insertion order by default, or the order produced by [`initialGroupOrderComparator`](#unsorted-group-order) if one is configured. If a group column had a sort applied and the user later explicitly clears that sort, the structural order is restored.

## Unsorted Group Order

When no sorting is applied, the groups are ordered by the order in which they appear in the data. This order can be overwritten with a custom initial order by providing an `initialGroupOrderComparator` grid option.

> **Note**
>
> As this is an initial order of groups, it executes before filtering and aggregation. This means it cannot use post-filtered data, or aggregated values as comparison criteria.

#### Initial Group Order

```ts
import {
  ClientSideRowModelModule,
  GridApi,
  GridOptions,
  InitialGroupOrderComparatorParams,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
} from "ag-grid-enterprise";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  RowGroupingModule,
  SetFilterModule,
]);

let gridApi: GridApi;

const gridOptions: GridOptions = {
  columnDefs: [
    { field: "country", rowGroup: true, hide: true },
    { field: "year", rowGroup: true, hide: true },
    { field: "athlete" },
    { field: "total" },
  ],
  defaultColDef: {
    flex: 1,
    minWidth: 100,
  },
  autoGroupColumnDef: {
    minWidth: 200,
  },
  groupDisplayType: "multipleColumns",
  initialGroupOrderComparator: (params: InitialGroupOrderComparatorParams) =>
    params.nodeA.allLeafChildren.length - params.nodeB.allLeafChildren.length,
};

const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);

fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
  .then((response) => response.json())
  .then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
```

[Live example: Initial Group Order](https://www.ag-grid.com/examples/grouping-sorting/initial-group-order/typescript)

The example above demonstrates the following configuration to order group rows based on the number of leaf children:

```js
const gridOptions = {
    initialGroupOrderComparator: (params) =>
        params.nodeA.allLeafChildren.length - params.nodeB.allLeafChildren.length,

    // other grid options ...
}
```
