# Source: https://mui.com/x/react-data-grid/server-side-data/row-grouping.md

# Source: https://mui.com/x/react-data-grid/row-grouping.md

---
title: Data Grid - Row grouping
---

# Data Grid - Row grouping [<span class="plan-premium"></span>](/x/introduction/licensing/#premium-plan 'Premium plan')

Group rows together based on column values in the Data Grid.

The Data Grid Premium provides a row grouping feature to create subsets of rows based on repeated column values or custom functions.

For example, in the demo below, movies are grouped based on their respective values in the **Company** column—try clicking the **>** on the left side of a row group to expand it:

```tsx
import {
  DataGridPremium,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingBasicExample() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium {...data} apiRef={apiRef} initialState={initialState} />
    </div>
  );
}

```

:::info
This document covers client-side implementation.
For row grouping on the server side, see [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/).
:::

## Initializing row grouping

To initialize row grouping, provide a model to the `initialState` prop on the `<DataGridPremium />` component.
The model's parameters correspond to the columns to be checked for repeating values.

```ts
<DataGridPremium
  initialState={{
    rowGrouping: {
      model: ['company', 'director'],
    },
  }}
/>
```

This example creates groups for the **Company** column with nested subgroups based on the **Director** column:

```tsx
import { DataGridPremium } from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingInitialState() {
  const data = useMovieData();

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        disableRowSelectionOnClick
        initialState={{
          rowGrouping: {
            model: ['company', 'director'],
          },
        }}
      />
    </div>
  );
}

```

## Controlled row grouping

Use the `rowGroupingModel` prop to control the state of the criteria used for grouping.
You can use the `onRowGroupingModelChange` prop to listen to changes to the grouping criteria and update the prop accordingly.

```tsx
import * as React from 'react';
import { DataGridPremium, GridRowGroupingModel } from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingControlled() {
  const data = useMovieData();

  const [rowGroupingModel, setRowGroupingModel] =
    React.useState<GridRowGroupingModel>(['company', 'director']);

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        rowGroupingModel={rowGroupingModel}
        onRowGroupingModelChange={(model) => setRowGroupingModel(model)}
      />
    </div>
  );
}

```

## Grouping columns

:::success
A note on terminology: **Grouping column** refers to the column that holds row groups.
This is sometimes—but not always—distinct from a **grouped column**, which is the column containing shared values that serve as the basis for row groups.
:::

### Single grouping column

By default, the Data Grid displays a single column that holds all grouped columns, no matter how many criteria are provided.
If there's only one criterion, the name of the grouping column will be the same as that of the grouped column from which it's derived.
When there are multiple criteria, the grouping column is named **Group**.

```tsx
import {
  DataGridPremium,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingSingleGroupingCol() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company', 'director'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium {...data} apiRef={apiRef} initialState={initialState} />
    </div>
  );
}

```

### Multiple grouping columns

To display a grouping column for each criterion, set the `rowGroupingColumnMode` prop to `multiple`.

```tsx
import {
  DataGridPremium,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingMultipleGroupingCol() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company', 'director'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        apiRef={apiRef}
        rowGroupingColumnMode="multiple"
        initialState={initialState}
      />
    </div>
  );
}

```

### Custom grouping column

Use the `groupingColDef` prop to customize the rendering of the grouping column.
You can override any property from the column definition (`GridColDef`) interface, with the exceptions of the `field`, the `type`, and the properties related to inline editing.

:::success
For better performance, keep `groupingColDef` prop reference stable between two renders.
:::

```tsx
import {
  DataGridPremium,
  GridGroupingColDefOverride,
  GridValidRowModel,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

const groupingColDef: GridGroupingColDefOverride<GridValidRowModel> = {
  headerName: 'Group',
};

export default function RowGroupingCustomGroupingColDefObject() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company', 'director'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        apiRef={apiRef}
        disableRowSelectionOnClick
        initialState={initialState}
        groupingColDef={groupingColDef}
      />
    </div>
  );
}

```

By default, when using the object format, the properties are applied to all grouping columns.
This means that if `rowGroupingColumnMode` is set to `multiple`, then all columns will share the same `groupingColDef` properties.

To override properties for specific grouping columns, or to apply different overrides based on the current grouping criteria, you can pass a callback function to `groupingColDef` instead of an object with its config.
The callback is called for each grouping column, and it receives the respective column's fields as parameters.

The demo below illustrates this approach to provide buttons for toggling between different grouping criteria:

```tsx
import * as React from 'react';
import {
  DataGridPremium,
  GridGroupingColDefOverride,
  GridGroupingColDefOverrideParams,
  GridRowModel,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';
import Stack from '@mui/material/Stack';
import Chip from '@mui/material/Chip';
import Box from '@mui/material/Box';

export default function RowGroupingCustomGroupingColDefCallback() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const [rowGroupingModel, setRowGroupingModel] = React.useState([
    'company',
    'director',
  ]);

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    rowGroupingModel,
  });

  const rowGroupingModelStr = rowGroupingModel.join('-');

  const groupingColDef = React.useCallback(
    (params: GridGroupingColDefOverrideParams) => {
      const override: GridGroupingColDefOverride = {};
      if (params.fields.includes('director')) {
        return {
          headerName: 'Director',
          valueFormatter: (value: string, row: GridRowModel) => {
            const rowId = apiRef.current?.getRowId(row);
            if (!rowId) {
              return undefined;
            }

            const rowNode = apiRef.current?.getRowNode(rowId);
            if (rowNode?.type === 'group' && rowNode?.groupingField === 'director') {
              return `by ${rowNode.groupingKey ?? ''}`;
            }
            return undefined;
          },
        };
      }

      return override;
    },
    [apiRef],
  );

  return (
    <div style={{ width: '100%' }}>
      <Stack
        sx={{ width: '100%', mb: 1 }}
        direction="row"
        alignItems="flex-start"
        columnGap={1}
      >
        <Chip
          label="Group by company"
          onClick={() => setRowGroupingModel(['company'])}
          variant="outlined"
          color={rowGroupingModelStr === 'company' ? 'primary' : undefined}
        />
        <Chip
          label="Group by company and director"
          onClick={() => setRowGroupingModel(['company', 'director'])}
          variant="outlined"
          color={rowGroupingModelStr === 'company-director' ? 'primary' : undefined}
        />
      </Stack>
      <Box sx={{ height: 400 }}>
        <DataGridPremium
          {...data}
          apiRef={apiRef}
          disableRowSelectionOnClick
          rowGroupingModel={rowGroupingModel}
          initialState={initialState}
          groupingColDef={groupingColDef}
        />
      </Box>
    </div>
  );
}

```

### Grouping rows with custom cell renderer

By default, when rows are grouped by a column that uses a [custom cell component](/x/react-data-grid/cells/), that component is also used for the cells in the grouping column.

For example, the demo below groups together movies based on the values in the **Rating** column.
Those cells contain a custom component with a star icon, and that same component is used to fill the grouping column.

```tsx
import * as React from 'react';
import Box from '@mui/material/Box';
import { useMovieData } from '@mui/x-data-grid-generator';
import { DataGridPremium, GridColDef } from '@mui/x-data-grid-premium';
import GradeIcon from '@mui/icons-material/Grade';

const ratingColDef: GridColDef = {
  field: 'imdbRating',
  headerName: 'Rating',
  type: 'number',
  width: 150,
  renderCell: (params) => {
    if (
      params.rowNode.type === 'group' &&
      params.field === params.rowNode.groupingField
    ) {
      return '';
    }
    return (
      <Box sx={{ display: 'flex', alignItems: 'center' }}>
        <GradeIcon htmlColor="#faaf00" />
        <span style={{ marginLeft: 2, marginRight: 8 }}>
          <b>{params.value}</b>/10
        </span>
      </Box>
    );
  },
};

export default function RowGroupingCustomCell() {
  const data = useMovieData();
  const columns = React.useMemo(
    () => [...data.columns, ratingColDef],
    [data.columns],
  );

  return (
    <Box sx={{ height: 350, width: '100%' }}>
      <DataGridPremium
        {...data}
        columns={columns}
        initialState={{ rowGrouping: { model: ['imdbRating'] } }}
      />
    </Box>
  );
}

```

You can opt out of this default behavior by returning `params.value` from the `renderCell()` function—this ensures that the grouping rows will only display the shared value rather than the entire component.

```tsx
const ratingColDef: GridColDef = {
  // ...
  renderCell: (params) => {
    if (params.rowNode.type === 'group') {
      return params.value;
    }

    return (
      // ...
    );
  },
};
```

```tsx
import * as React from 'react';
import Box from '@mui/material/Box';
import { useMovieData } from '@mui/x-data-grid-generator';
import { DataGridPremium, GridColDef } from '@mui/x-data-grid-premium';
import GradeIcon from '@mui/icons-material/Grade';

const ratingColDef: GridColDef = {
  field: 'imdbRating',
  headerName: 'Rating',
  type: 'number',
  width: 150,
  renderCell: (params) => {
    if (params.rowNode.type === 'group') {
      return params.value;
    }
    return (
      <Box sx={{ display: 'flex', alignItems: 'center' }}>
        <GradeIcon htmlColor="#faaf00" />
        <span style={{ marginLeft: 2, marginRight: 8 }}>
          <b>{params.value}</b>/10
        </span>
      </Box>
    );
  },
};

export default function RowGroupingCustomCellDefault() {
  const data = useMovieData();
  const columns = React.useMemo(
    () => [...data.columns, ratingColDef],
    [data.columns],
  );

  return (
    <Box sx={{ height: 350, width: '100%' }}>
      <DataGridPremium
        {...data}
        columns={columns}
        initialState={{ rowGrouping: { model: ['imdbRating'] } }}
      />
    </Box>
  );
}

```

### Show values for leaves

By default, grouped rows display no value in their grouping column cells—these cells are called **leaves**.
To display a value in a leaf, provide the `leafField` property to the `groupingColDef`.

The demo below uses `leafField` to display values from the **Title** column in the leaves.

```tsx
import {
  DataGridPremium,
  GridGroupingColDefOverride,
  GridValidRowModel,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

const groupingColDef: GridGroupingColDefOverride<GridValidRowModel> = {
  leafField: 'title',
};

export default function RowGroupingLeafWithValue() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      columns: {
        columnVisibilityModel: {
          // Hide the column used for leaves
          title: false,
        },
      },
      rowGrouping: {
        model: ['company'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        apiRef={apiRef}
        groupingColDef={groupingColDef}
        initialState={initialState}
      />
    </div>
  );
}

```

### Hide descendant count

By default, row group cells display the number of descendant rows they contain, in parentheses.
Use the `hideDescendantCount` property of the `groupingColDef` to hide this number.

```tsx
import {
  DataGridPremium,
  GridGroupingColDefOverride,
  GridValidRowModel,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

const groupingColDef: GridGroupingColDefOverride<GridValidRowModel> = {
  hideDescendantCount: true,
};

export default function RowGroupingHideDescendantCount() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        apiRef={apiRef}
        disableRowSelectionOnClick
        initialState={initialState}
        groupingColDef={groupingColDef}
      />
    </div>
  );
}

```

### Hide grouped columns

By default, [grouped columns](#grouping-columns) remain visible when grouping is applied.
This means that when there's only one grouping criterion, the grouping column and the grouped column both display the same values, which may be redundant or unnecessary.
You can use the `useKeepGroupedColumnsHidden` utility hook to hide grouped columns.
This automatically hides grouped columns when added to the model, and displays them when removed.

The two examples below show how to use `columnVisibilityModel` and `initialState` with the `useKeepGroupedColumnsHidden` hook.
You can mix the two examples to support both at the same time.

```tsx
// Usage with the initial state
const apiRef = useGridApiRef();

const initialState = useKeepGroupedColumnsHidden({
  apiRef,
  initialState: {
    rowGrouping: {
      model: ['company'],
    },
    columns: {
      // Other hidden columns
      columnVisibilityModel: { gross: false },
    },
  },
});

return <DataGridPremium {...data} apiRef={apiRef} initialState={initialState} />;
```

```tsx
// Usage with the controlled model
const apiRef = useGridApiRef();

const [rowGroupingModel, setRowGroupingModel] = React.useState([
  'company',
  'director',
]);

const initialState = useKeepGroupedColumnsHidden({
  apiRef,
  rowGroupingModel,
});

return (
  <DataGridPremium
    {...data}
    apiRef={apiRef}
    initialState={initialState}
    rowGroupingModel={rowGroupingModel}
  />
);
```

```tsx
import {
  DataGridPremium,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingUseKeepGroupedColumnsHidden() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium {...data} apiRef={apiRef} initialState={initialState} />
    </div>
  );
}

```

## Disable row grouping

### For all columns

To disable row grouping for all columns, set the `disableRowGrouping` prop to `true`.
This disables all features related to row grouping, unless a model is provided, in which case row grouping is set to read-only mode as described in [Grouping non-groupable columns programmatically](#grouping-non-groupable-columns-programmatically).

```tsx
import { DataGridPremium } from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingDisabled() {
  const data = useMovieData();

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium {...data} disableRowGrouping />
    </div>
  );
}

```

### For specific columns

To disable grouping for a specific column, set the `groupable` property on its `GridColDef` to `false`.

In the example below, the **Director** column cannot be grouped, even though there are repeating values.
(The **Title** and **Gross** columns cannot be grouped in any examples in this doc because there are no repeating values.)

```tsx
import * as React from 'react';
import {
  DataGridPremium,
  GridColDef,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingColDefCanBeGrouped() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company'],
      },
    },
  });

  const columnWithNoDirectorGroup = React.useMemo<GridColDef[]>(
    () =>
      data.columns.map((colDef) =>
        colDef.field === 'director' ? { ...colDef, groupable: false } : colDef,
      ),
    [data.columns],
  );

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        apiRef={apiRef}
        columns={columnWithNoDirectorGroup}
        disableRowSelectionOnClick
        initialState={initialState}
      />
    </div>
  );
}

```

### Grouping non-groupable columns programmatically

To apply row grouping programmatically on non-groupable columns (columns with `groupable: false` in the `GridColDef`), you can provide the row grouping model in one of three ways:

1. Pass `rowGrouping.model` to the `initialState` prop. This [initializes grouping](/x/react-data-grid/row-grouping/#initializing-row-grouping) with the provided model.
2. Provide the `rowGroupingModel` prop. This [controls grouping](/x/react-data-grid/row-grouping/#controlled-row-grouping) with the provided model.
3. Call the API method `setRowGroupingModel`. This sets the aggregation with the provided model.

In the following example, the **Company** column is not groupable through the interface, but the `rowGroupingModel` prop is passed to generate a read-only row group.

```tsx
import * as React from 'react';
import { DataGridPremium, GridRowGroupingModel } from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingReadOnly() {
  const data = useMovieData();

  const columns = React.useMemo(
    () =>
      data.columns.map((c) => ({
        ...c,
        groupable: c.field !== 'company',
      })),
    [data.columns],
  );

  const [rowGroupingModel, setRowGroupingModel] =
    React.useState<GridRowGroupingModel>(['company', 'director']);

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        columns={columns}
        rowGroupingModel={rowGroupingModel}
        onRowGroupingModelChange={(model) => setRowGroupingModel(model)}
      />
    </div>
  );
}

```

## Using groupingValueGetter() for complex grouping value

The grouping value must be either a string, a number, `Date`, `null`, or `undefined`.
If your cell value is more complex, or if you want to use a different value for grouping than the one displayed in the cell, use the `groupingValueGetter()` property in the column definition:

```ts
const columns: GridColDef[] = [
  {
    field: 'year',
    type: 'number',
    valueFormatter: (value) => (value ? `${value}` : ''),
    // Group by decade
    groupingValueGetter: (value) => {
      const yearDecade = Math.floor(value / 10) * 10;
      return `${yearDecade.toString().slice(-2)}'s`;
    },
  },
  // ...
];
```

```tsx
import * as React from 'react';
import {
  DataGridPremium,
  useGridApiRef,
  GridColDef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingGroupingValueGetter() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const columnsWithComposer = React.useMemo<GridColDef[]>(
    () =>
      data.columns.map((column) => {
        if (column.field === 'year') {
          return {
            field: 'year',
            headerName: 'Year',
            type: 'number',
            groupingValueGetter: (value) => {
              const yearDecade = Math.floor(value / 10) * 10;
              return `${yearDecade.toString().slice(-2)}'s`;
            },
            valueFormatter: (value) => (value ? `${value}` : ''),
            availableAggregationFunctions: ['max', 'min'],
          };
        }
        return column;
      }),

    [data.columns],
  );

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['year'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        columns={columnsWithComposer}
        apiRef={apiRef}
        initialState={initialState}
      />
    </div>
  );
}

```

:::info
If your column also has a `valueGetter` property, the value passed to the `groupingValueGetter` method will still be the row value from the `row[field]`.
:::

## Rows with missing groups

If a grouping criterion's key is `null` or `undefined` for a given row, the Data Grid will treat that row as if it doesn't have a value and exclude it from grouping.

The demo below illustrates this behavior—movies are grouped by **Cinematic Universe**, and rows with no value for this column are displayed individually before those with values are displayed in groups:

```tsx
import {
  DataGridPremium,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingRowsWithMissingGroups() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['cinematicUniverse'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        apiRef={apiRef}
        disableRowSelectionOnClick
        initialState={initialState}
      />
    </div>
  );
}

```

## Group expansion

By default, all groups are initially displayed collapsed.
You can change this behavior by setting the `defaultGroupingExpansionDepth` prop to expand all the groups up to a given depth when loading the data.
To expand the whole tree, set `defaultGroupingExpansionDepth = -1`.

```tsx
import {
  DataGridPremium,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingDefaultExpansionDepth() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company', 'director'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        apiRef={apiRef}
        defaultGroupingExpansionDepth={-1}
        disableRowSelectionOnClick
        initialState={initialState}
      />
    </div>
  );
}

```

Use the `isGroupExpandedByDefault()` prop to expand groups by default according to more complex logic.
This prop is a callback that receives a node as an argument.
When defined, this callback always takes priority over the `defaultGroupingExpansionDepth` prop.

```tsx
isGroupExpandedByDefault={
  node => node.groupingField === 'company' && node.groupingKey === '20th Century Fox'
}
```

The example below uses this pattern to render the Grid with the **20th Century Fox** group expanded:

```tsx
import {
  DataGridPremium,
  GridGroupNode,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

const isGroupExpandedByDefault = (node: GridGroupNode) =>
  node.groupingField === 'company' && node.groupingKey === '20th Century Fox';

export default function RowGroupingIsGroupExpandedByDefault() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company', 'director'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        apiRef={apiRef}
        isGroupExpandedByDefault={isGroupExpandedByDefault}
        disableRowSelectionOnClick
        initialState={initialState}
      />
    </div>
  );
}

```

Use the `setRowChildrenExpansion()` method on [the `apiRef` object](#apiref) to programmatically set the expansion of a row.
Changing the expansion of a row emits a `rowExpansionChange` event that you can listen for to react to the expansion change.

The demo below uses this pattern to implement the expansion toggle button:

```tsx
import * as React from 'react';
import {
  DataGridPremium,
  GRID_ROOT_GROUP_ID,
  GridGroupNode,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';

const debug = (params: GridGroupNode) =>
  console.info('Row expansion changed for row ', params.id);

export default function RowGroupingSetChildrenExpansion() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  React.useEffect(() => {
    apiRef.current?.subscribeEvent('rowExpansionChange', debug);
  }, [apiRef]);

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company'],
      },
    },
  });

  const toggle2ndGroup = () => {
    const groups =
      apiRef.current?.getRowNode<GridGroupNode>(GRID_ROOT_GROUP_ID)!.children;

    if (groups && groups.length > 1) {
      const groupId = groups[1];
      apiRef.current?.setRowChildrenExpansion(
        groupId,
        !apiRef.current.getRowNode<GridGroupNode>(groupId)!.childrenExpanded,
      );
    }
  };

  return (
    <Box sx={{ width: '100%' }}>
      <Button size="small" onClick={toggle2ndGroup}>
        Toggle 2nd row expansion
      </Button>
      <Box sx={{ height: 400, pt: 1 }}>
        <DataGridPremium
          {...data}
          apiRef={apiRef}
          disableRowSelectionOnClick
          initialState={initialState}
        />
      </Box>
    </Box>
  );
}

```

:::warning
The `apiRef.current.setRowChildrenExpansion()` method is not compatible with [server-side tree data](/x/react-data-grid/server-side-data/tree-data/) or [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/).
Use `apiRef.current.dataSource.fetchRows()` instead.
:::

### Customize grouping cell indent

Use the `--DataGrid-cellOffsetMultiplier` CSS variable to change the default cell indentation, as shown here:

```tsx
<DataGridPremium
  sx={{
    // default value is 2
    '--DataGrid-cellOffsetMultiplier': 6,
  }}
/>
```

```tsx
import {
  DataGridPremium,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingCustomCellIndent() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company', 'director'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        sx={{
          '--DataGrid-cellOffsetMultiplier': 6,
        }}
        apiRef={apiRef}
        initialState={initialState}
      />
    </div>
  );
}

```

## Sorting and filtering

### Single grouping column

When using `rowGroupingColumnMode = "single"`, the default behavior is to sort each grouping criterion using the column's `sortComparator`, then apply the `filterOperators` from the top-level grouping criteria.

If you're rendering leaves with the `leafField` property of the `groupingColDef`, then sorting and filtering will be applied on the leaves based on the `sortComparator` and `filterOperators` of their original column.

You can force the filtering to be applied to other grouping criteria using the `mainGroupingCriteria` property of `groupingColDef`.

```tsx
import * as React from 'react';
import {
  DataGridPremium,
  GridGroupingColDefOverride,
  GridValidRowModel,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';
import Box from '@mui/material/Box';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import Select from '@mui/material/Select';

export default function RowGroupingFilteringSingleGroupingColDef() {
  const data = useMovieData();
  const [mainGroupingCriteria, setMainGroupingCriteria] =
    React.useState<string>('undefined');

  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company', 'director'],
      },
    },
  });

  const groupingColDef: GridGroupingColDefOverride<GridValidRowModel> =
    React.useMemo(
      () => ({
        mainGroupingCriteria:
          mainGroupingCriteria === 'undefined' ? undefined : mainGroupingCriteria,
      }),
      [mainGroupingCriteria],
    );

  return (
    <Box sx={{ width: '100%' }}>
      <FormControl fullWidth>
        <InputLabel
          htmlFor="main-grouping-criteria"
          id="main-grouping-criteria-label"
        >
          Main grouping criteria
        </InputLabel>
        <Select
          label="Main grouping criteria"
          onChange={(event) => setMainGroupingCriteria(event.target.value)}
          value={mainGroupingCriteria}
          id="main-grouping-criteria"
          labelId="main-grouping-criteria-label"
        >
          <MenuItem value="undefined">Default behavior</MenuItem>
          <MenuItem value="company">Company</MenuItem>
          <MenuItem value="director">Director</MenuItem>
        </Select>
      </FormControl>
      <Box sx={{ height: 400, pt: 1 }}>
        <DataGridPremium
          {...data}
          apiRef={apiRef}
          disableRowSelectionOnClick
          defaultGroupingExpansionDepth={-1}
          initialState={initialState}
          groupingColDef={groupingColDef}
        />
      </Box>
    </Box>
  );
}

```

### Multiple grouping columns

When using `rowGroupingColumnMode = "multiple"`, the default behavior is to apply the `sortComparator` and `filterOperators` of the grouping criteria of each grouping column.

If you're rendering leaves in one of those columns with the `leafField` property of `groupingColDef`, then sorting and filtering will be applied on the leaves for this grouping column based on the `sortComparator` and `filterOperators` of their original column.

If you want to render leaves but apply the sorting and filtering on the grouping criteria of the column, you can force it by setting the `mainGroupingCriteria` property `groupingColDef` to be equal to the grouping criteria.

In the example below, sorting and filtering from the **Company** grouping column are applied to the `company` field, while sorting and filtering from the **Director** grouping column are applied to the `director` field even though it has leaves.

```tsx
import {
  DataGridPremium,
  GridGroupingColDefOverrideParams,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

const groupingColDef = (params: GridGroupingColDefOverrideParams) =>
  params.fields.includes('director')
    ? {
        leafField: 'title',
        mainGroupingCriteria: 'director',
      }
    : {};

export default function RowGroupingSortingMultipleGroupingColDef() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company', 'director'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        apiRef={apiRef}
        disableRowSelectionOnClick
        defaultGroupingExpansionDepth={-1}
        initialState={initialState}
        rowGroupingColumnMode="multiple"
        groupingColDef={groupingColDef}
      />
    </div>
  );
}

```

:::warning
If you're dynamically switching the `leafField` or `mainGroupingCriteria`, sorting and filtering models will not be cleaned up automatically, and the sorting or filtering will not be reapplied.
:::

## Automatic parent and child selection

By default, selecting a parent row also selects all of its descendants.
You can customize this behavior using the `rowSelectionPropagation` prop.
Here's how it's structured:

```ts
type GridRowSelectionPropagation = {
  descendants?: boolean; // default: true
  parents?: boolean; // default: true
};
```

When `rowSelectionPropagation.descendants` is set to `true`:

- Selecting a parent selects all of its filtered descendants automatically
- Deselecting a parent row deselects of all its filtered descendants automatically

When `rowSelectionPropagation.parents` is set to `true`:

- Selecting all the filtered descendants of a parent also selects the parent automatically
- Deselecting a descendant of a selected parent also deselects the parent automatically

The example below demonstrates the usage of the `rowSelectionPropagation` prop—use the checkboxes at the top to see how the selection behavior changes between parents and children.

```tsx
import * as React from 'react';
import {
  DataGridPremium,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
  GridRowSelectionPropagation,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import Stack from '@mui/material/Stack';

export default function RowGroupingPropagateSelection() {
  const data = useMovieData();
  const apiRef = useGridApiRef();
  const [rowSelectionPropagation, setRowSelectionPropagation] =
    React.useState<GridRowSelectionPropagation>({
      parents: false,
      descendants: false,
    });

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company', 'director'],
      },
    },
  });

  return (
    <div style={{ width: '100%' }}>
      <Stack direction="row" spacing={2}>
        <FormControlLabel
          control={
            <Checkbox
              checked={rowSelectionPropagation.descendants}
              onChange={(event) =>
                setRowSelectionPropagation((prev) => ({
                  ...prev,
                  descendants: event.target.checked,
                }))
              }
            />
          }
          label="Auto-select descendants"
        />
        <FormControlLabel
          control={
            <Checkbox
              checked={rowSelectionPropagation.parents}
              onChange={(event) =>
                setRowSelectionPropagation((prev) => ({
                  ...prev,
                  parents: event.target.checked,
                }))
              }
            />
          }
          label="Auto-select parents"
        />
      </Stack>
      <div style={{ height: 400 }}>
        <DataGridPremium
          {...data}
          apiRef={apiRef}
          initialState={initialState}
          checkboxSelection
          rowSelectionPropagation={rowSelectionPropagation}
        />
      </div>
    </div>
  );
}

```

Row selection propagation also affects the **Select all** checkbox like any other checkbox group.
Selected rows that do not pass the filtering criteria are automatically deselected when the filter is applied.
Row selection propagation is not applied to the unfiltered rows.

:::warning
Row selection propagation has some limitations:

- If `props.disableMultipleRowSelection` is set to `true`, then row selection propagation won't apply.

- Row selection propagation is a client-side feature and does not support [server-side data](/x/react-data-grid/server-side-data/).

- If you're using the state setter method `apiRef.current.setRowSelectionModel()`, you must explicitly compute the selection model with the rows that have propagation changes applied using `apiRef.current.getPropagatedRowSelectionModel()` and pass it as shown below:

  ```ts
  const selectionModelWithPropagation =
    apiRef.current.getPropagatedRowSelectionModel({
      type: 'include',
      ids: new Set([1, 2, 3]),
    });
  apiRef.current.setRowSelectionModel(selectionModelWithPropagation);
  ```

  See [the `apiRef` section below](/x/react-data-grid/row-selection/#apiref) for the signatures of these methods.

- If you're using the `keepNonExistentRowsSelected` prop, then row selection propagation will not automatically apply to the rows being added that were part of the selection model but didn't exist in the previous rows.
  Consider opening a [GitHub issue](https://github.com/mui/mui-x/issues/new?template=2.feature.yml) if you need this behavior.

:::

## Drag-and-drop group reordering

With row reordering, users can reorder row groups or move rows from one group to another.
To enable this feature with row grouping, pass the `rowReordering` prop to the Data Grid Premium component:

```tsx
<DataGridPremium rowGroupingModel={['category']} rowReordering />
```

```tsx
import {
  DataGridPremium,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData } from '@mui/x-data-grid-generator';

export default function RowGroupingReordering() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company', 'director'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        apiRef={apiRef}
        initialState={initialState}
        rowReordering
      />
    </div>
  );
}

```

### Reacting to group updates

When a row is moved from one group to another, it warrants a row update, and the row data value that was used to group this row must be updated to maintain the row grouping data integrity.

For example, in a Data Grid displaying movies grouped by companies, if the **Avatar** row is moved from **20th Century Fox** to the **Disney Studios** group, along with the row being updated in the row tree, the row data must be updated to reflect this change.

```diff
 // "Avatar" row
 {
  title: 'Avatar',
- company: '20th Century Fox',
+ company: 'Disney Studios',
  ...
 }
```

The Data Grid updates the internal row data, but for the change to persist on the server, you must use the [`processRowUpdate()`](/x/react-data-grid/editing/persistence/#the-processrowupdate-callback) callback.

When moving a nested group to a different parent on the same depth, there will be multiple `processRowUpdate()` calls for all the leaf descendants of the dragged group. In case some of them fail, you might see the parent node appearing in both places. Consider using the `onProcessRowUpdateError()` to show the proper feedback to the user.

### Usage with groupingValueSetter()

If you use [`colDef.groupingValueGetter()`](#using-groupingvaluegetter-for-complex-grouping-value) to handle complex grouping values and you want to group across rows, you must use the `colDef.groupingValueSetter()` to properly convert back the simple value to the complex one.

This method should return the updated row based on the groupKey (`value`) that corresponds to the target group.

```ts
const columns: GridColDef[] = [
  {
    field: 'composer',
    valueGetter: (value) => value?.name,
    groupingValueSetter: (value, row) => ({
      ...row,
      composer: {
        ...row.composer,
        name: value,
      },
    }),
  },
  // ...
];
```

```tsx
import * as React from 'react';
import {
  DataGridPremium,
  useGridApiRef,
  GridColDef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useMovieData, Movie } from '@mui/x-data-grid-generator';

export default function RowGroupingGroupingValueSetter() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const columnsWithComposer = React.useMemo<GridColDef[]>(
    () => [
      ...data.columns,
      {
        field: 'composer',
        headerName: 'Composer',
        valueGetter: (value: { name: string }) => value?.name,
        groupingValueSetter: (value, row) => {
          return {
            ...row,
            composer: {
              ...row.composer,
              name: value,
            },
          };
        },
        width: 200,
      } as GridColDef<Movie, string>,
      {
        field: 'decade',
        headerName: 'Decade',
        valueGetter: (_, row) => (row.year ? Math.floor(row.year / 10) * 10 : null),
        groupingValueSetter: (value, row) => ({
          ...row,
          // Since converting to decade is a lossy operation, directly using the decade value should be sufficient here
          year: value,
        }),
        valueFormatter: (value: number | null) =>
          value ? `${value.toString().slice(-2)}'s` : '',
      } as GridColDef<Movie, number, string>,
    ],
    [data.columns],
  );

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['composer', 'decade'],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        columns={columnsWithComposer}
        apiRef={apiRef}
        initialState={initialState}
        rowReordering
      />
    </div>
  );
}

```

## Get all rows in a group

Use the `apiRef.current.getRowGroupChildren()` method to get the IDs of all rows in a group.
The results will not contain autogenerated rows such as subgroup rows or aggregation footers.

```ts
const rows: GridRowId[] = apiRef.current.getRowGroupChildren({
  groupId: params.id,

  // If true, the rows will be in the order displayed on screen
  applySorting: true,

  // If true, only the rows matching the current filters will be returned
  applyFiltering: true,
});
```

Use `getGroupRowIdFromPath()` to get row IDs from within all groups that match a given grouping criterion:

```ts
const rows = apiRef.current.getRowGroupChildren({
  groupId: getGroupRowIdFromPath([{ field: 'company', key: 'Disney Studios' }]),
});
```

```tsx
import * as React from 'react';
import {
  DataGridPremium,
  GridEventListener,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { Movie, useMovieData } from '@mui/x-data-grid-generator';
import Alert from '@mui/material/Alert';

export default function RowGroupingGetRowGroupChildren() {
  const data = useMovieData();
  const apiRef = useGridApiRef();

  const [lastGroupClickedChildren, setLastGroupClickedChildren] = React.useState<
    string[] | null
  >(null);

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      rowGrouping: {
        model: ['company', 'director'],
      },
    },
  });

  const handleRowClick = React.useCallback<GridEventListener<'rowClick'>>(
    (params) => {
      // Only log groups
      if (apiRef.current?.getRowNode(params.id)?.type !== 'group') {
        return;
      }

      const rowIds = apiRef.current.getRowGroupChildren({
        groupId: params.id,
      });

      const rowTitles = rowIds.map(
        (rowId) => apiRef.current!.getRow<Movie>(rowId)!.title,
      );

      setLastGroupClickedChildren(rowTitles);
    },
    [apiRef],
  );

  return (
    <div style={{ width: '100%' }}>
      <div style={{ height: 400, width: '100%' }}>
        <DataGridPremium
          {...data}
          apiRef={apiRef}
          onRowClick={handleRowClick}
          hideFooter
          initialState={initialState}
        />
      </div>
      <Alert severity="info" sx={{ mb: 1 }}>
        {lastGroupClickedChildren ? (
          <code>
            Movies in the last group clicked
            <br />
            <br />
            {lastGroupClickedChildren.map((movieTitle) => (
              <React.Fragment>
                - {movieTitle}
                <br />
              </React.Fragment>
            ))}
          </code>
        ) : (
          <code>Click on a group row to log its children here</code>
        )}
      </Alert>
    </div>
  );
}

```

:::warning
The `apiRef.current.getRowGroupChildren()` method is not compatible with [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/) because not all rows may be available to retrieve in any given instance.
:::

## Row group panel 🚧

:::warning
This feature isn't available yet, but it is planned—you can 👍 upvote [this GitHub issue](https://github.com/mui/mui-x/issues/5235) to help us prioritize it.
Please don't hesitate to leave a comment there to describe your needs, especially if you have a use case we should address or you're facing specific pain points with your current solution.
:::

With the row group panel, users would be able to control which columns are used for grouping by dragging them inside the panel.

## Advanced use cases

The demo below provides an example of row grouping that's closer to a real-world use case for this feature, grouping a large dataset of contacts based on the types of goods they provide (via the hidden **Commodity** column).

See [Row grouping recipes](/x/react-data-grid/recipes-row-grouping/) for more advanced use cases.

```tsx
import {
  DataGridPremium,
  GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD,
  GridGroupingColDefOverride,
  GridValidRowModel,
  useGridApiRef,
  useKeepGroupedColumnsHidden,
} from '@mui/x-data-grid-premium';
import { useDemoData } from '@mui/x-data-grid-generator';

const groupingColDef: GridGroupingColDefOverride<GridValidRowModel> = {
  leafField: 'traderEmail',
};

export default function RowGroupingFullExample() {
  const { data, loading } = useDemoData({
    dataSet: 'Commodity',
    rowLength: 100,
    maxColumns: 25,
  });
  const apiRef = useGridApiRef();

  const initialState = useKeepGroupedColumnsHidden({
    apiRef,
    initialState: {
      ...data.initialState,
      rowGrouping: {
        ...data.initialState?.rowGrouping,
        model: ['commodity'],
      },
      sorting: {
        sortModel: [{ field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, sort: 'asc' }],
      },
    },
  });

  return (
    <div style={{ height: 400, width: '100%' }}>
      <DataGridPremium
        {...data}
        apiRef={apiRef}
        loading={loading}
        disableRowSelectionOnClick
        initialState={initialState}
        groupingColDef={groupingColDef}
      />
    </div>
  );
}

```

## apiRef

The Data Grid exposes a set of methods via the `apiRef` object that are used internally in the implementation of the row grouping feature.
The reference below describes the relevant functions.
See [API object](/x/react-data-grid/api-object/) for more details.

:::warning
This API should only be used as a last resort when the Data Grid's built-in props aren't sufficient for your specific use case.
:::

```jsx
import ApiDocs from 'docsx/src/modules/components/ApiDocs';
import api from 'docsx/pages/x/api/data-grid/grid-row-grouping-api.json';

export default function RowGroupingApiNoSnap() {
  return <ApiDocs premiumApi={api} />;
}

```

## API

- [DataGrid](/x/api/data-grid/data-grid/)
- [DataGridPro](/x/api/data-grid/data-grid-pro/)
- [DataGridPremium](/x/api/data-grid/data-grid-premium/)