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

# Row Grouping - Grouping Data

Enable grouping on a column to group rows by equivalent values.

## Enabling Row Grouping

Row Grouping is enabled by setting `rowGroup` to `true` on one or more [Column Definition](https://www.ag-grid.com/javascript-data-grid/column-definitions/). Parent rows are then introduced for each unique value in that column, containing the rows with that value.

#### Basic Grouping

```ts
import {
  ClientSideRowModelModule,
  GridApi,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule } from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";

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

ModuleRegistry.registerModules([ClientSideRowModelModule, RowGroupingModule]);

let gridApi: GridApi<IOlympicData>;

const gridOptions: GridOptions<IOlympicData> = {
  columnDefs: [
    { field: "country", rowGroup: true, hide: true },
    { field: "year" },
    { field: "athlete" },
    { field: "sport" },
    { field: "gold" },
    { field: "silver" },
    { field: "bronze" },
  ],
  defaultColDef: {
    flex: 1,
    minWidth: 100,
  },
  autoGroupColumnDef: {
    minWidth: 200,
  },
};

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: Basic Grouping](https://www.ag-grid.com/examples/grouping-data/basic-grouping/typescript)

The example above uses the following configuration to group rows by their `country` values:

```js
const gridOptions = {
    columnDefs: [
        { field: 'country', rowGroup: true },
        // ...other column definitions
    ],

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

## Grouping by Multiple Columns

When grouping on multiple columns using `rowGroup`, the order of columns within the column definitions is used to determine which column to group by first. This can be overridden with a custom order by providing the `rowGroupIndex` property in each grouped columns definition.

#### Grouping by Multiple Columns

```ts
import {
  ClientSideRowModelModule,
  GridApi,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule } from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";

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

ModuleRegistry.registerModules([ClientSideRowModelModule, RowGroupingModule]);

let gridApi: GridApi<IOlympicData>;

const gridOptions: GridOptions<IOlympicData> = {
  columnDefs: [
    { field: "country", rowGroupIndex: 1, hide: true },
    { field: "year", rowGroupIndex: 0, hide: true },
    { field: "athlete" },
    { field: "sport" },
    { field: "gold" },
    { field: "silver" },
    { field: "bronze" },
  ],
  defaultColDef: {
    flex: 1,
    minWidth: 100,
  },
  autoGroupColumnDef: {
    minWidth: 200,
  },
  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: Grouping by Multiple Columns](https://www.ag-grid.com/examples/grouping-data/row-group-index/typescript)

The example above demonstrates the following configuration for grouping rows by `year` first, and `country` second:

```js
const gridOptions = {
    columnDefs: [
        { field: 'country', rowGroupIndex: 1 },
        { field: 'year', rowGroupIndex: 0 },
        // ...other column definitions
    ],

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

## Grouping on Object Data

When grouping on object data, the grid needs a way to compare items to determine if they are equivalent. Setting a `keyCreator` on the grouped column definition provides the grid with string keys it can compare.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `keyCreator` | `KeyCreatorFunc` |  |  | Function to return a string key for a value. This string is used for grouping, Set filtering, and searching within cell editor dropdowns. When filtering and searching the string is exposed to the user, so make sure to return a human-readable value. |

The following example uses a custom set of rows, each containing an `athlete` field that maps to objects with `id` and `name` properties.

#### Grouping by Object Data

```ts
import {
  ClientSideRowModelModule,
  GridApi,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule } from "ag-grid-enterprise";
import { getData } from "./data";

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

ModuleRegistry.registerModules([ClientSideRowModelModule, RowGroupingModule]);

let gridApi: GridApi;

const gridOptions: GridOptions = {
  columnDefs: [
    {
      field: "athlete",
      rowGroup: true,
      hide: true,
      keyCreator: (params) => params.value.id,
      valueFormatter: (params) => params.value.name,
    },
    { field: "country" },
    { field: "year" },
    { field: "gold" },
    { field: "silver" },
    { field: "bronze" },
  ],
  defaultColDef: {
    flex: 1,
    minWidth: 100,
  },
  autoGroupColumnDef: {
    minWidth: 200,
  },
  rowData: getData(),
};

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

[Live example: Grouping by Object Data](https://www.ag-grid.com/examples/grouping-data/grouping-object-data/typescript)

This demonstrates the following configuration for grouping rows by the `athlete` objects by their `id` property:

```js
const gridOptions = {
    columnDefs: [
        {
            field: 'athlete',
            rowGroup: true,
            keyCreator: (params) => params.value.id,
            valueFormatter: (params) => params.value.name,
        },
        // ...other column definitions
    ],

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

## Grouping by Dates and Times

When grouping by date/time values, the grid can optionally group by components of the date/time.

To enable grouping by parts of the date for a particular column, use the `groupHierarchy` property of the [Column Definition](https://www.ag-grid.com/javascript-data-grid/column-properties/#reference-grouping-groupHierarchy).

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `groupHierarchy` | [`(GroupHierarchyParts \| string \| ColDef)[]`](https://www.ag-grid.com/javascript-data-grid/column-properties/) |  |  | Specify a grouping hierarchy for this column. This generates one or more virtual columns to group or pivot by when this column is grouped or pivoted. This can be used to group/pivot by values derived from a source column. The grid provides hierarchy types related to date components. Users can provide their own hierarchy types by specifying a `ColDef`, or referring to the name of a hierarchy type defined in `groupHierarchyConfig`. Modules (any of): [`RowGroupingModule`](https://www.ag-grid.com/javascript-data-grid/modules/), [`PivotModule`](https://www.ag-grid.com/javascript-data-grid/modules/). |

```js
const gridOptions = {
    columnDefs: [
        {
            field: 'date',
            rowGroup: true,
            groupHierarchy: ['year', 'month']
        },
        // ...other column definitions
    ],

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

This snippet is illustrated in the example below.

#### Grouping by Dates and Times

```ts
import {
  ClientSideRowModelModule,
  ColDef,
  GridApi,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnsToolPanelModule,
  PivotModule,
  RowGroupingModule,
  RowGroupingPanelModule,
  SideBarModule,
} from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";

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

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  RowGroupingModule,
  SideBarModule,
  ColumnsToolPanelModule,
  RowGroupingPanelModule,
  PivotModule,
]);

let gridApi: GridApi<IOlympicData>;

const COL_DEFS: ColDef<IOlympicData>[] = [
  {
    field: "date",
    rowGroup: true,
    enableRowGroup: true,
    enablePivot: true,
    groupHierarchy: ["year", "month"],
    minWidth: 120,
  },
  { field: "country" },
  { field: "sport" },
  { field: "total", aggFunc: "sum" },
];

const gridOptions: GridOptions<IOlympicData> = {
  columnDefs: COL_DEFS,
  defaultColDef: {
    flex: 1,
    minWidth: 100,
  },
  autoGroupColumnDef: {
    minWidth: 225,
  },
  sideBar: "columns",
  rowGroupPanelShow: "always",
};

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

fetch("https://www.ag-grid.com/example-assets/small-olympic-winners.json")
  .then((response) => response.json())
  .then((data: IOlympicData[]) =>
    gridApi!.setGridOption(
      "rowData",
      data.map((d) => ({
        ...d,
        date: d.date?.split("/").reverse().join("-"),
      })),
    ),
  );

function onChangeFormattedMonth(event: any) {
  const month = event.target.checked ? "formattedMonth" : "month";
  COL_DEFS[0].groupHierarchy![1] = month;
  gridApi.setGridOption("columnDefs", COL_DEFS);
}

if (typeof window !== "undefined") {
  // Attach external event handlers to window so they can be called from index.html
  (<any>window).onChangeFormattedMonth = onChangeFormattedMonth;
}
```

[Live example: Grouping by Dates and Times](https://www.ag-grid.com/examples/grouping-data/grouping-date-time/typescript)

> **Note**
>
> By default, the grid requires datetime values to be formatted using the ISO-8601 format in order to be correctly parsed into their components.
>
> To use other date formats, provide a custom [Cell Data Type Definition](https://www.ag-grid.com/javascript-data-grid/cell-data-types/#providing-custom-cell-data-types) for the `dateString` and/or `dateTimeString` data types.

## Defining Custom Grouping Hierarchies

When using `groupHierarchy` as demonstrated in [Grouping by Dates and Times](https://www.ag-grid.com/javascript-data-grid/grouping-data/#grouping-by-dates-and-times), the grid provides built-in support for several components of a date/time value.

Users may instead provide definitions of their own components via the `groupHierarchyConfig` grid option. These definitions may then be used in the `groupHierarchy` property of a column definition.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `groupHierarchyConfig` | `GroupHierarchyConfig` |  |  | Custom group hierarchy components can be defined here for later use in `colDef.groupHierarchy` Module: [`RowGroupingModule`](https://www.ag-grid.com/javascript-data-grid/modules/). |

The following example illustrates this by defining a custom grouping hierarchy component that allows grouping by the week number:

#### Grouping by Dates and Times

```ts
import {
  ClientSideRowModelModule,
  ColumnApiModule,
  GridApi,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnsToolPanelModule,
  PivotModule,
  RowGroupingModule,
  RowGroupingPanelModule,
  SideBarModule,
} from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";

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

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  RowGroupingModule,
  SideBarModule,
  ColumnsToolPanelModule,
  RowGroupingPanelModule,
  ColumnApiModule,
  PivotModule,
]);

let gridApi: GridApi<IOlympicData>;

const gridOptions: GridOptions<IOlympicData> = {
  columnDefs: [
    {
      field: "date",
      rowGroup: true,
      enableRowGroup: true,
      enablePivot: true,
      groupHierarchy: ["year", "week"],
      minWidth: 120,
    },
    { field: "country" },
    { field: "sport" },
    { field: "total", aggFunc: "sum" },
  ],
  defaultColDef: {
    flex: 1,
    minWidth: 100,
  },
  autoGroupColumnDef: {
    minWidth: 225,
  },
  sideBar: "columns",
  rowGroupPanelShow: "always",
  groupHierarchyConfig: {
    week: {
      headerValueGetter: (params) => {
        const sourceCol = params.api
          .getColumns()
          ?.find((col) => col.getColDef().field === "date");
        if (!sourceCol) return "";

        const name = params.api.getDisplayNameForColumn(
          sourceCol,
          params.location,
        );

        return `${name} (Week)`;
      },
      valueGetter: (params) => {
        const sourceCol = params.api
          .getColumns()
          ?.find((col) => col.getColDef().field === "date");

        const field = sourceCol?.getColDef().field;
        if (!field) return;

        const value = params.getValue(field);
        const date = getDate(value);
        if (!date) return;

        return getWeekNumber(date).toString();
      },
    },
  },
};

function getDate(value: any): Date | null {
  if (value instanceof Date) {
    return value;
  }
  if (typeof value === "string") {
    const [year, month, day] = value.split("-");
    const d = new Date();
    d.setFullYear(parseInt(year, 10), parseInt(month, 10), parseInt(day, 10));
    d.setHours(0, 0, 0, 0);
    return d;
  }
  return null;
}

function getWeekNumber(date: Date): number {
  const d = new Date(date.getTime());
  d.setHours(0, 0, 0, 0);
  d.setDate(d.getDate() + 3 - ((date.getDay() + 6) % 7));
  const week1 = new Date(d.getFullYear(), 0, 4);
  return (
    1 +
    Math.round(
      ((d.getTime() - week1.getTime()) / 86400000 -
        3 +
        ((week1.getDay() + 6) % 7)) /
        7,
    )
  );
}

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

fetch("https://www.ag-grid.com/example-assets/small-olympic-winners.json")
  .then((response) => response.json())
  .then((data: IOlympicData[]) =>
    gridApi!.setGridOption(
      "rowData",
      data.map((d) => ({
        ...d,
        date: d.date?.split("/").reverse().join("-"),
      })),
    ),
  );
```

[Live example: Grouping by Dates and Times](https://www.ag-grid.com/examples/grouping-data/grouping-custom-date-time/typescript)

## Grouping on Null and Undefined Data

When grouping `null`, `undefined` or `""` (empty string) values the grid will group these together under the heading `(Blanks)` as the final group.

By setting the `groupAllowUnbalanced` property to `true`, the grid will instead display these rows without a group.

#### Grouping by Null and Undefined Data

```ts
import {
  ClientSideRowModelModule,
  GridApi,
  GridOptions,
  ModuleRegistry,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule } from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";

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

ModuleRegistry.registerModules([ClientSideRowModelModule, RowGroupingModule]);

let gridApi: GridApi<IOlympicData>;

const gridOptions: GridOptions<IOlympicData> = {
  columnDefs: [
    { field: "country", rowGroup: true, hide: true },
    { field: "year" },
    { field: "athlete" },
    { field: "sport" },
    { field: "gold" },
    { field: "silver" },
    { field: "bronze" },
  ],
  defaultColDef: {
    flex: 1,
    minWidth: 100,
  },
  autoGroupColumnDef: {
    minWidth: 200,
  },
};

function toggleGroupAllowUnbalanced() {
  const enable = document.querySelector<HTMLInputElement>(
    "#groupAllowUnbalanced",
  )!.checked;
  gridApi.setGridOption("groupAllowUnbalanced", enable);
}

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

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

if (typeof window !== "undefined") {
  // Attach external event handlers to window so they can be called from index.html
  (<any>window).toggleGroupAllowUnbalanced = toggleGroupAllowUnbalanced;
}
```

[Live example: Grouping by Null and Undefined Data](https://www.ag-grid.com/examples/grouping-data/grouping-null-undefined/typescript)

To enable unbalanced grouping, the following configuration is used:

```js
const gridOptions = {
    groupAllowUnbalanced: true,

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

## Hiding Parents of Individual Rows

Groups with only a single child can be hidden from the grid by setting the `groupHideParentOfSingleChild` grid option to `true`. To remove only groups with a single leaf child, set this option to `"leafGroupsOnly"` instead.

Filtering does not impact which groups get removed. Only groups containing a single child prior to filtering being applied are removed.

#### Removing Single Children

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

// 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: "athlete" },
    { field: "country", rowGroup: true },
    { field: "city", rowGroup: true },
    { field: "year" },
    { field: "gold", aggFunc: "sum" },
    { field: "silver", aggFunc: "sum" },
    { field: "bronze", aggFunc: "sum" },
  ],
  defaultColDef: {
    flex: 1,
    minWidth: 150,
  },
  autoGroupColumnDef: {
    headerName: "Group",
    field: "athlete",
    minWidth: 220,
    cellRenderer: "agGroupCellRenderer",
  },
  rowData: getData(),

  // expand everything by default
  groupDefaultExpanded: -1,

  suppressAggFuncInHeader: true,
};

function onOptionChange() {
  const key = (
    document.querySelector("#input-display-type") as HTMLSelectElement
  ).value;
  if (key === "true" || key === "false") {
    gridApi!.setGridOption("groupHideParentOfSingleChild", key === "true");
  } else {
    gridApi!.setGridOption("groupHideParentOfSingleChild", "leafGroupsOnly");
  }
}

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

if (typeof window !== "undefined") {
  // Attach external event handlers to window so they can be called from index.html
  (<any>window).onOptionChange = onOptionChange;
}
```

[Live example: Removing Single Children](https://www.ag-grid.com/examples/grouping-data/remove-single-children/typescript)

The following is an example of the configuration used to hide all parents of a single row:

```js
const gridOptions = {
    groupHideParentOfSingleChild: true,

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

> **Note**
>
> The properties `groupHideParentOfSingleChild` and `groupHideOpenParents` are mutually exclusive.
