---
title: "Master / Detail - Master Rows"
enterprise: true
framework: vue
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.

```ts
<ag-grid-vue
    :masterDetail="masterDetail"
    /* other grid options ... */>
</ag-grid-vue>

// by itself, all rows will be expandable
this.masterDetail = true;
```

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/vue-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/vue-data-grid/modules/). |

```ts
<ag-grid-vue
    :masterDetail="masterDetail"
    :isRowMaster="isRowMaster"
    /* other grid options ... */>
</ag-grid-vue>

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

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

#### Dynamic Master Rows

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IDetailCellRendererParams,
  IsRowMaster,
  ModuleRegistry,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  MasterDetailModule,
} from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

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

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :masterDetail="true"
      :isRowMaster="isRowMaster"
      :columnDefs="columnDefs"
      :defaultColDef="defaultColDef"
      :detailCellRendererParams="detailCellRendererParams"
      :rowData="rowData"
      @first-data-rendered="onFirstDataRendered"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const isRowMaster = ref<IsRowMaster>((dataItem: any) => {
      return dataItem ? dataItem.callRecords.length > 0 : false;
    });
    const columnDefs = ref<ColDef[]>([
      // group cell renderer needed for expand / collapse icons
      { field: "name", cellRenderer: "agGroupCellRenderer" },
      { field: "account" },
      { field: "calls" },
      { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
    ]);
    const defaultColDef = ref<ColDef>({
      flex: 1,
    });
    const detailCellRendererParams = ref<any>({
      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>);
    const rowData = ref<any[]>(null);

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

      const updateData = (data) => {
        rowData.value = data;
      };

      fetch(
        "https://www.ag-grid.com/example-assets/master-detail-dynamic-data.json",
      )
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      isRowMaster,
      columnDefs,
      defaultColDef,
      detailCellRendererParams,
      rowData,
      onGridReady,
      onFirstDataRendered,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

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

## 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/vue-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 {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IDetailCellRendererParams,
  IsRowMaster,
  ModuleRegistry,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  MasterDetailModule,
} from "ag-grid-enterprise";
import { IAccount } from "./interfaces";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

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

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="display: flex; flex-direction: column; height: 100%">
      <div style="padding-bottom: 4px">
        <button v-on:click="onBtClearMilaCalls()">Clear Mila Calls</button>
        <button v-on:click="onBtSetMilaCalls()">Set Mila Calls</button>
      </div>
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :masterDetail="true"
        :isRowMaster="isRowMaster"
        :columnDefs="columnDefs"
        :defaultColDef="defaultColDef"
        :getRowId="getRowId"
        :detailCellRendererParams="detailCellRendererParams"
        :rowData="rowData"
        @first-data-rendered="onFirstDataRendered"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi<IAccount> | null>(null);
    const isRowMaster = ref<IsRowMaster>((dataItem: any) => {
      return dataItem ? dataItem.callRecords.length > 0 : false;
    });
    const columnDefs = ref<ColDef[]>([
      // group cell renderer needed for expand / collapse icons
      { field: "name", cellRenderer: "agGroupCellRenderer" },
      { field: "account" },
      { field: "calls" },
      { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
    ]);
    const defaultColDef = ref<ColDef>({
      flex: 1,
    });
    const getRowId = ref<GetRowIdFunc>((params: GetRowIdParams) =>
      String(params.data.account),
    );
    const detailCellRendererParams = ref({
      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>);
    const rowData = ref<IAccount[]>(null);

    function onFirstDataRendered(params: FirstDataRenderedEvent) {
      // arbitrarily expand a row for presentational purposes
      setTimeout(() => {
        params.api.getDisplayedRowAtIndex(1)!.setExpanded(true);
      }, 0);
    }
    function onBtClearMilaCalls() {
      const milaSmithRowNode = gridApi.value!.getRowNode("177001")!;
      const milaSmithData = milaSmithRowNode.data!;
      milaSmithData.callRecords = [];
      milaSmithData.calls = milaSmithData.callRecords.length;
      gridApi.value!.applyTransaction({ update: [milaSmithData] });
    }
    function onBtSetMilaCalls() {
      const milaSmithRowNode = gridApi.value!.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.value!.applyTransaction({ update: [milaSmithData] });
    }
    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => {
        rowData.value = data;
      };

      fetch(
        "https://www.ag-grid.com/example-assets/master-detail-dynamic-data.json",
      )
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      isRowMaster,
      columnDefs,
      defaultColDef,
      getRowId,
      detailCellRendererParams,
      rowData,
      onGridReady,
      onFirstDataRendered,
      onBtClearMilaCalls,
      onBtSetMilaCalls,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

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

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 {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import "./style.css";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IDetailCellRendererParams,
  IsRowMaster,
  ModuleRegistry,
  RowApiModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  MasterDetailModule,
} from "ag-grid-enterprise";
import CallsCellRenderer from "./callsCellRendererVue";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

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

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :masterDetail="true"
      :isRowMaster="isRowMaster"
      :columnDefs="columnDefs"
      :defaultColDef="defaultColDef"
      :getRowId="getRowId"
      :detailCellRendererParams="detailCellRendererParams"
      :rowData="rowData"
      @first-data-rendered="onFirstDataRendered"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
    CallsCellRenderer,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const isRowMaster = ref<IsRowMaster>((dataItem: any) => {
      return dataItem ? dataItem.callRecords.length > 0 : false;
    });
    const columnDefs = ref<ColDef[]>([
      // group cell renderer needed for expand / collapse icons
      { field: "name", cellRenderer: "agGroupCellRenderer" },
      { field: "account" },
      { field: "calls", cellRenderer: "CallsCellRenderer" },
      { field: "minutes", valueFormatter: "x.toLocaleString() + 'm'" },
    ]);
    const defaultColDef = ref<ColDef>({
      flex: 1,
    });
    const getRowId = ref<GetRowIdFunc>((params: GetRowIdParams) =>
      String(params.data.account),
    );
    const detailCellRendererParams = ref<any>({
      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>);
    const rowData = ref<any[]>(null);

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

      const updateData = (data) => {
        rowData.value = data;
      };

      fetch(
        "https://www.ag-grid.com/example-assets/master-detail-dynamic-data.json",
      )
        .then((resp) => resp.json())
        .then((data) => updateData(data));
    };

    return {
      gridApi,
      isRowMaster,
      columnDefs,
      defaultColDef,
      getRowId,
      detailCellRendererParams,
      rowData,
      onGridReady,
      onFirstDataRendered,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

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

## 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/vue-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/vue-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.
