---
title: "Master / Detail - Master Rows"
enterprise: true
framework: javascript
version: "36.1.0"
---

# Master / Detail - Master Rows

Master Rows are the rows inside the Master Grid that can be expanded to display Detail Grids.

## Static Master Rows

Once a Master Grid is configured with `masterDetail=true`, all rows in the Master Grid behave as Master Rows, in that they can be expanded to display Detail Grids.

```js
const gridOptions = {
    // by itself, all rows will be expandable
    masterDetail: true,

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

Because Static Master Rows are used in all the basic examples of Master / Detail, another example is not given here.

## Dynamic Master Rows

Dynamic Master Rows allows specifically deciding what rows in the Master Grid can be expanded. This can be useful if, for example, a Master Row has no child records, then it may not be desirable to allow expanding the Master Row.

To specify which rows should expand, provide the grid callback `isRowMaster`. The callback will be called once for each row. Return `true` to allow expanding and `false` to disallow expanding for that row.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `isRowMaster` | `IsRowMaster` |  |  | Callback to be used with [Master Detail](https://www.ag-grid.com/javascript-data-grid/master-detail/) to determine if a row should be a master row. If `false` is returned no detail row will exist for this row. Module: [`MasterDetailModule`](https://www.ag-grid.com/javascript-data-grid/modules/). |

```js
const gridOptions = {
    // turn on master detail
    masterDetail: true,
    // specify which rows to expand
    isRowMaster: dataItem => {
        return expandThisRow ? true : false;
    },

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

The following example only shows detail rows when there are corresponding child records.

#### Dynamic Master Rows

```ts
import {
  ClientSideRowModelModule,
  FirstDataRenderedEvent,
  GridApi,
  GridOptions,
  IDetailCellRendererParams,
  ModuleRegistry,
  RowApiModule,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  MasterDetailModule,
} from "ag-grid-enterprise";

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

ModuleRegistry.registerModules([
  RowApiModule,
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  MasterDetailModule,
  ColumnMenuModule,
  ContextMenuModule,
]);

let gridApi: GridApi;

const gridOptions: GridOptions = {
  masterDetail: true,
  isRowMaster: (dataItem: any) => {
    return dataItem ? dataItem.callRecords.length > 0 : false;
  },
  columnDefs: [
    // group cell renderer needed for expand / collapse icons
    { field: "name", cellRenderer: "agGroupCellRenderer" },
    { field: "account" },
    { field: "calls" },
    { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
  ],
  defaultColDef: {
    flex: 1,
  },
  detailCellRendererParams: {
    detailGridOptions: {
      columnDefs: [
        { field: "callId" },
        { field: "direction" },
        { field: "number", minWidth: 150 },
        { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
        { field: "switchCode", minWidth: 150 },
      ],
      defaultColDef: {
        flex: 1,
      },
    },
    getDetailRowData: function (params) {
      params.successCallback(params.data.callRecords);
    },
  } as IDetailCellRendererParams<IAccount, ICallRecord>,
  onFirstDataRendered: onFirstDataRendered,
};

function onFirstDataRendered(params: FirstDataRenderedEvent) {
  // arbitrarily expand a row for presentational purposes
  setTimeout(() => {
    params.api.getDisplayedRowAtIndex(1)!.setExpanded(true);
  }, 0);
}

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

fetch("https://www.ag-grid.com/example-assets/master-detail-dynamic-data.json")
  .then((response) => response.json())
  .then(function (data) {
    gridApi!.setGridOption("rowData", data);
  });
```

[Live example: Dynamic Master Rows](https://www.ag-grid.com/examples/master-detail-master-rows/dynamic/typescript/)

## Changing Dynamic Master Rows

The callback `isRowMaster` is re-called after data changes in the row as a result of a [Transaction Update](https://www.ag-grid.com/javascript-data-grid/data-update-transactions/). This gives the opportunity to change whether the row is expandable or not.

```js
// to get isRowMaster called again, update the row using a Transaction Update
const transaction = { update: [ updatedRecord1, updatedRecord2 ] };
gridApi.applyTransaction(transaction);
```

In the example below, only Master Rows that have data to show are expandable. Note the following:

- Row 'Nora Thomas' has no detail records, thus is not expandable.
- Row 'Mila Smith' has detail records, thus is expandable.
- Clicking 'Clear Mila Calls' removes detail records from Mila Smith which results in the Mila Smith row no longer being a Master Row.
- Clicking 'Set Mila Calls' sets detail records from Mila Smith which results in the Mila Smith becoming a Master Row.

#### Dynamically Changing Master Rows

```ts
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  FirstDataRenderedEvent,
  GetRowIdParams,
  GridApi,
  GridOptions,
  IDetailCellRendererParams,
  ModuleRegistry,
  RowApiModule,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  MasterDetailModule,
} from "ag-grid-enterprise";
import { IAccount, ICallRecord } from "./interfaces";

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

ModuleRegistry.registerModules([
  ClientSideRowModelApiModule,
  RowApiModule,
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  MasterDetailModule,
  ColumnMenuModule,
  ContextMenuModule,
]);

let gridApi: GridApi<IAccount>;

const gridOptions: GridOptions<IAccount> = {
  masterDetail: true,
  isRowMaster: (dataItem: any) => {
    return dataItem ? dataItem.callRecords.length > 0 : false;
  },
  columnDefs: [
    // group cell renderer needed for expand / collapse icons
    { field: "name", cellRenderer: "agGroupCellRenderer" },
    { field: "account" },
    { field: "calls" },
    { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
  ],
  defaultColDef: {
    flex: 1,
  },
  getRowId: (params: GetRowIdParams) => String(params.data.account),
  detailCellRendererParams: {
    detailGridOptions: {
      columnDefs: [
        { field: "callId" },
        { field: "direction" },
        { field: "number", minWidth: 150 },
        { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
        { field: "switchCode", minWidth: 150 },
      ],
      defaultColDef: {
        flex: 1,
      },
    },
    getDetailRowData: (params) => {
      params.successCallback(params.data.callRecords);
    },
  } as IDetailCellRendererParams<IAccount, ICallRecord>,
  onFirstDataRendered: onFirstDataRendered,
};

function onFirstDataRendered(params: FirstDataRenderedEvent) {
  // arbitrarily expand a row for presentational purposes
  setTimeout(() => {
    params.api.getDisplayedRowAtIndex(1)!.setExpanded(true);
  }, 0);
}

function onBtClearMilaCalls() {
  const milaSmithRowNode = gridApi!.getRowNode("177001")!;
  const milaSmithData = milaSmithRowNode.data!;
  milaSmithData.callRecords = [];
  milaSmithData.calls = milaSmithData.callRecords.length;
  gridApi!.applyTransaction({ update: [milaSmithData] });
}

function onBtSetMilaCalls() {
  const milaSmithRowNode = gridApi!.getRowNode("177001")!;
  const milaSmithData = milaSmithRowNode.data!;
  milaSmithData.callRecords = [
    {
      name: "susan",
      callId: 579,
      duration: 23,
      switchCode: "SW5",
      direction: "Out",
      number: "(02) 47485405",
    },
    {
      name: "susan",
      callId: 580,
      duration: 52,
      switchCode: "SW3",
      direction: "In",
      number: "(02) 32367069",
    },
  ];
  milaSmithData.calls = milaSmithData.callRecords.length;
  gridApi!.applyTransaction({ update: [milaSmithData] });
}

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

fetch("https://www.ag-grid.com/example-assets/master-detail-dynamic-data.json")
  .then((response) => response.json())
  .then(function (data) {
    gridApi!.setGridOption("rowData", data);
  });

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

[Live example: Dynamically Changing Master Rows](https://www.ag-grid.com/examples/master-detail-master-rows/changing-dynamic-1/typescript/)

The example below extends the previous example. It demonstrates a common scenario of the Master Row controlling the Detail Rows. Note the following:

- Each Master Row has buttons to add or remove one detail row.
- Clicking 'Add' will:
  - Add one detail row.
  - Ensure the Master Row is expandable.
  - Ensure the Master Row is expanded (i.e. the Detail Grid is visible).
- Clicking 'Remove' will:
  - Remove one detail row.
  - If no detail rows exist, ensure Master Row is not expandable

#### Dynamically Changing Master Rows

```ts
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  FirstDataRenderedEvent,
  GetRowIdParams,
  GridApi,
  GridOptions,
  IDetailCellRendererParams,
  ModuleRegistry,
  RowApiModule,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  MasterDetailModule,
} from "ag-grid-enterprise";
import { CallsCellRenderer } from "./callsCellRenderer";

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

ModuleRegistry.registerModules([
  RowApiModule,
  ClientSideRowModelModule,
  ClientSideRowModelApiModule,
  ColumnsToolPanelModule,
  MasterDetailModule,
  ColumnMenuModule,
  ContextMenuModule,
]);

let gridApi: GridApi;

const gridOptions: GridOptions = {
  masterDetail: true,
  isRowMaster: (dataItem: any) => {
    return dataItem ? dataItem.callRecords.length > 0 : false;
  },
  columnDefs: [
    // group cell renderer needed for expand / collapse icons
    { field: "name", cellRenderer: "agGroupCellRenderer" },
    { field: "account" },
    { field: "calls", cellRenderer: CallsCellRenderer },
    { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
  ],
  defaultColDef: {
    flex: 1,
  },
  getRowId: (params: GetRowIdParams) => String(params.data.account),
  detailCellRendererParams: {
    detailGridOptions: {
      columnDefs: [
        { field: "callId" },
        { field: "direction" },
        { field: "number", minWidth: 150 },
        { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
        { field: "switchCode", minWidth: 150 },
      ],
      defaultColDef: {
        flex: 1,
      },
    },
    getDetailRowData: (params) => {
      params.successCallback(params.data.callRecords);
    },
  } as IDetailCellRendererParams<IAccount, ICallRecord>,
  onFirstDataRendered: onFirstDataRendered,
};

function onFirstDataRendered(params: FirstDataRenderedEvent) {
  // arbitrarily expand a row for presentational purposes
  setTimeout(() => {
    params.api.getDisplayedRowAtIndex(1)!.setExpanded(true);
  }, 0);
}

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

fetch("https://www.ag-grid.com/example-assets/master-detail-dynamic-data.json")
  .then((response) => response.json())
  .then(function (data) {
    gridApi!.setGridOption("rowData", data);
  });
```

[Live example: Dynamically Changing Master Rows](https://www.ag-grid.com/examples/master-detail-master-rows/changing-dynamic-2/typescript/)

## Opening Master Rows by Default

Master Rows can be expanded by default using either `masterDefaultExpanded` or `isMasterOpenByDefault`.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `masterDefaultExpanded` | `number` |  |  | Master Detail: set to the number of levels of master rows to expand by default, e.g. `0` for none, `1` for first level only, etc. Set to `-1` to expand everything. If not set, falls back to `groupDefaultExpanded`. Module: [`MasterDetailModule`](https://www.ag-grid.com/javascript-data-grid/modules/). |
| `isMasterOpenByDefault` | `IsMasterOpenByDefault` |  |  | (Client-side Row Model only) Master Detail: allows master rows to be open by default. Module: [`MasterDetailModule`](https://www.ag-grid.com/javascript-data-grid/modules/). |

Set `masterDefaultExpanded` to the number of levels of Master Rows to expand by default, or `-1` to expand all Master Rows. If not set, it falls back to `groupDefaultExpanded`.

```js
const gridOptions = {
    // expand all master rows by default
    masterDefaultExpanded: -1,
};
```

For finer control, provide the `isMasterOpenByDefault` callback. It is called once for each Master Row; return `true` to expand that row by default.

```js
const gridOptions = {
    // expand specific master rows by default
    isMasterOpenByDefault: (params) => {
        return params.data.shouldExpand;
    },
};
```

> **Note**
>
> `isMasterOpenByDefault` applies to Master Rows, whereas `isGroupOpenByDefault` applies to group rows. When combining Master Detail with row grouping, each callback controls only its own row type.
