---
title: "SSRM Master Detail"
enterprise: true
framework: vue
version: "36.1.0"
---

# SSRM Master Detail

This section shows how the Server-Side Row Model can be configured with a Master / Detail view.

The ability to nest grids within grids is commonly referred to as Master / Detail. Here the top-level grid is referred to as the 'master grid' and the nested grid is referred to as the 'detail grid'.

Master / Details is configured the same way for the Server-Side Row Model and the Client-Side Row Model. For a comprehensive look at Master / Detail configurations, see: [Client-Side Master / Detail](https://www.ag-grid.com/vue-data-grid/master-detail/).

Because the configuration is already discussed in [Client-Side Master / Detail](https://www.ag-grid.com/vue-data-grid/master-detail/), this page focuses on areas that are of particular interest to this Server-Side version.

## Enabling Master / Detail

To enable Master / Detail, you should set the following grid options:

- **masterDetail:** Set to `true` to inform the grid you want to allow expanding of rows to reveal detail grids.
- **detailGridOptions:** The grid options to set for the detail grid. The detail grid is a fully featured instance of AG Grid, so any configuration can be set on it that you would set on any other grid.
- **getDetailRowData:** A function you implement to provide the grid with rows for display in the detail grids.

These grid options are illustrated below:

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

// master grid columns
this.columnDefs = [];
// use the server-side row model
this.rowModelType = 'serverSide';
// enable master detail
this.masterDetail = true;
this.detailCellRendererParams = {
    detailGridOptions: {
        // detail grid columns
        columnDefs: [],
    },
    getDetailRowData: params => {
        // supply data to the detail grid
        params.successCallback(params.data);
    }
};
```

> **Note**
>
> Note that the nested detail grid can be configured to use any Row Model.

## Example: Infinite Scrolling with Master / Detail

This example shows a simple Master / Detail with the Server-Side Row Model. From this example notice the following:

- **masterDetail** - is set to `true` in the master grid options.
- **detailCellRendererParams** - specifies the `detailGridOptions` to use and `getDetailRowData` extracts the data for the detail row.
- **cellRenderer: 'agGroupCellRenderer'** - is used to provide expand / collapse icons on the master rows.

#### Infinite Scrolling with Master / Detail

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

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

function getServerSideDatasource(server: any): IServerSideDatasource {
  return {
    getRows: (params) => {
      // adding delay to simulate real server call
      setTimeout(() => {
        const response = server.getResponse(params.request);
        if (response.success) {
          // call the success callback
          params.success({
            rowData: response.rows,
            rowCount: response.lastRow,
          });
        } else {
          // inform the grid request failed
          params.fail();
        }
      }, 500);
    },
  };
}

function getFakeServer(allData: any) {
  return {
    getResponse: (request: IServerSideGetRowsRequest) => {
      console.log(
        "asking for rows: " + request.startRow + " to " + request.endRow,
      );
      // take a slice of the total rows
      const rowsThisPage = allData.slice(request.startRow, request.endRow);
      // if row count is known, it's possible to skip over blocks
      const lastRow = allData.length;
      return {
        success: true,
        rows: rowsThisPage,
        lastRow: lastRow,
      };
    },
  };
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="height: 100%; box-sizing: border-box">
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :columnDefs="columnDefs"
        :defaultColDef="defaultColDef"
        :rowModelType="rowModelType"
        :masterDetail="true"
        :detailCellRendererParams="detailCellRendererParams"
        :isServerSideGroupOpenByDefault="isServerSideGroupOpenByDefault"
        :rowData="rowData"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      // group cell renderer needed for expand / collapse icons
      { field: "accountId", cellRenderer: "agGroupCellRenderer" },
      { field: "name" },
      { field: "country" },
      { field: "calls" },
      { field: "totalDuration" },
    ]);
    const defaultColDef = ref<ColDef>({
      flex: 1,
      sortable: false,
    });
    const rowModelType = ref<RowModelType>("serverSide");
    const detailCellRendererParams = ref<any>({
      detailGridOptions: {
        columnDefs: [
          { field: "callId" },
          { field: "direction" },
          { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
          { field: "switchCode", minWidth: 150 },
          { field: "number", minWidth: 180 },
        ],
        defaultColDef: {
          flex: 1,
        },
      },
      getDetailRowData: (params) => {
        // supply details records to detail cell renderer (i.e. detail grid)
        params.successCallback(params.data.callRecords);
      },
    } as IDetailCellRendererParams<IAccount, ICallRecord>);
    const isServerSideGroupOpenByDefault = ref<IsServerSideGroupOpenByDefault>(
      (params) => params.rowNode.id === "0",
    );
    const rowData = ref<any[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      const updateData = (data) => {
        const server = getFakeServer(data);
        const datasource = getServerSideDatasource(server);
        params.api!.setGridOption("serverSideDatasource", datasource);
      };

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

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      rowModelType,
      detailCellRendererParams,
      isServerSideGroupOpenByDefault,
      rowData,
      onGridReady,
    };
  },
});

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

[Live example: Infinite Scrolling with Master / Detail](https://www.ag-grid.com/examples/server-side-model-master-detail/infinite-scrolling/vue3)

## Combining Row Grouping with Master Detail

It is possible to combine [Server-Side Grouping](https://www.ag-grid.com/vue-data-grid/server-side-model-grouping/) with Master Detail.

The following snippet shows row grouping on the 'country' column by setting `rowGroup = true`:

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

this.columnDefs = [
    { field: 'country', rowGroup: true },

    // more column definitions
];
```

## Example: Row Grouping with Master Detail

Below shows Row Grouping combined with Master / Detail. From the example you can notice the following:

- **rowGroup** - is set to `true` on the 'country' column definition.
- **masterDetail** - is set to `true` to enable Master / Detail.
- **detailCellRendererParams** - specifies the `detailGridOptions` to use and `getDetailRowData` extracts the data for the detail row.
- **autoGroupColumnDef** - is used to specify which column in the master row should be included in the group hierarchy.

#### Row Grouping with Master Detail

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

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

function getServerSideDatasource(server: any): IServerSideDatasource {
  return {
    getRows: (params) => {
      console.log("[Datasource] - rows requested by grid: ", params.request);
      const response = server.getData(params.request);
      // adding delay to simulate real server call
      setTimeout(() => {
        if (response.success) {
          // call the success callback
          params.success({
            rowData: response.rows,
            rowCount: response.lastRow,
          });
        } else {
          // inform the grid request failed
          params.fail();
        }
      }, 200);
    },
  };
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :columnDefs="columnDefs"
      :defaultColDef="defaultColDef"
      :autoGroupColumnDef="autoGroupColumnDef"
      :rowModelType="rowModelType"
      :masterDetail="true"
      :detailCellRendererParams="detailCellRendererParams"
      :rowData="rowData"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      { field: "country", rowGroup: true, hide: true },
      { field: "accountId", hide: true },
      { field: "name" },
      { field: "calls" },
      { field: "totalDuration" },
    ]);
    const defaultColDef = ref<ColDef>({
      flex: 1,
    });
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      field: "accountId",
    });
    const rowModelType = ref<RowModelType>("serverSide");
    const detailCellRendererParams = ref<any>({
      detailGridOptions: {
        columnDefs: [
          { field: "callId" },
          { field: "direction" },
          { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
          { field: "switchCode" },
          { field: "number" },
        ],
        defaultColDef: {
          flex: 1,
        },
      },
      getDetailRowData: (params) => {
        // supply details records to detail cell renderer (i.e. detail grid)
        params.successCallback(params.data.callRecords);
      },
    } as IDetailCellRendererParams<IAccount, ICallRecord>);
    const rowData = ref<any[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      setTimeout(() => {
        // expand some master row
        const someRow = params.api.getRowNode("1");
        if (someRow) {
          someRow.setExpanded(true);
        }
      }, 1000);

      const updateData = (data) => {
        // setup the fake server with entire dataset
        const fakeServer = FakeServer(data);
        // create datasource with a reference to the fake server
        const datasource = getServerSideDatasource(fakeServer);
        // register the datasource with the grid
        params.api!.setGridOption("serverSideDatasource", datasource);
      };

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

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      autoGroupColumnDef,
      rowModelType,
      detailCellRendererParams,
      rowData,
      onGridReady,
    };
  },
});

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

[Live example: Row Grouping with Master Detail](https://www.ag-grid.com/examples/server-side-model-master-detail/row-grouping/vue3)

### Expanding Master Rows

Normally, parent rows (groups) can be expanded and child rows cannot be expanded (unless they are themselves parents). This means that normally only parent rows have expand / collapse icons.

For Master / Detail, expand and collapse icons are also needed at the master level. When doing Master / Detail, expand and collapse icons are also needed to expand the child rows where those rows are also master rows.

Rather than use the `autoGroupColumnDef` for the master rows as shown in the example above, simply specify a group cell renderer on the column that should show the expand / collapse icons.

This is shown in the code snippet below:

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

this.columnDefs = [
    { field: 'country', rowGroup: true },
    { field: 'accountId', maxWidth: 200, cellRenderer: 'agGroupCellRenderer' },

    // more column definitions
];
```

You can use `expandAll()` to expand all master rows along with their associated detail grids. To ensure that master rows not yet loaded in the viewport are also expanded, set `ssrmExpandAllAffectsAllRows` to true, just as you would with SSRM Row Grouping. For more information, see [SSRM Expand All / Collapse All](https://www.ag-grid.com/vue-data-grid/server-side-model-grouping/#expand-all--collapse-all).

> **Note**
>
> Note that `ssrmExpandAllAffectsAllRows` is not automatically propagated to nested detail grids. Even if set to `true` on the master grid, it only controls expansion at the master level. To expand all rows in nested detail grids (e.g., in a hierarchy of master-detail grids), you must explicitly set `ssrmExpandAllAffectsAllRows: true` on each detail grid’s `detailGridOptions`.

## Combining Tree Data with Master Detail

It is possible to combine [Tree Data](https://www.ag-grid.com/vue-data-grid/server-side-model-tree-data/) with Master Detail in the Server-Side Row Model. This allows to display hierarchical (tree-structured) data, where each master row can expand to show a detail grid.

To enable this, set both `treeData: true` and `masterDetail: true` in your grid options. You will also need to provide the required callbacks for tree data:

- **isServerSideGroup(dataItem):** Returns `true` if the row is a group (has children).
- **getServerSideGroupKey(dataItem):** Returns the key for the group node (e.g., an ID).

The detail grid is configured using `detailCellRendererParams`, just as in other Master Detail scenarios.

Example configuration:

```js
const gridOptions = {
    columnDefs: [
        { field: 'employeeName', cellRenderer: 'agGroupCellRenderer' },
        { field: 'jobTitle' },
        { field: 'employmentType' }
    ],
    rowModelType: 'serverSide',
    treeData: true,
    masterDetail: true,
    isServerSideGroup: dataItem => !!dataItem.children,
    getServerSideGroupKey: dataItem => dataItem.employeeId,
    detailCellRendererParams: {
        detailGridOptions: {
            columnDefs: [
                { field: 'project' },
                { field: 'duration' }
            ]
        },
        getDetailRowData: params => {
            params.successCallback(params.data.projects || []);
        }
    }
};
```

## Example: Tree Data with Master Detail

#### Tree Data with Master Detail

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

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

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <ag-grid-vue
      style="width: 100%; height: 100%;"
      @grid-ready="onGridReady"
      :rowModelType="rowModelType"
      :serverSideDatasource="serverSideDatasource"
      :columnDefs="columnDefs"
      :defaultColDef="defaultColDef"
      :treeData="true"
      :isServerSideGroup="isServerSideGroup"
      :getServerSideGroupKey="getServerSideGroupKey"
      :autoGroupColumnDef="autoGroupColumnDef"
      :masterDetail="true"
      :detailRowHeight="detailRowHeight"
      :detailCellRendererParams="detailCellRendererParams"
      :isRowMaster="isRowMaster"
      :rowData="rowData"></ag-grid-vue>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const rowModelType = ref<RowModelType>("serverSide");
    const serverSideDatasource = ref<IServerSideDatasource>(
      new MyServerSideDatasource(),
    );
    const columnDefs = ref<ColDef[]>([{ field: "info" }]);
    const defaultColDef = ref<ColDef>({ flex: 1 });
    const isServerSideGroup = ref<IsServerSideGroup>(
      (dataItem) => !!dataItem.children,
    );
    const getServerSideGroupKey = ref<GetServerSideGroupKey>(
      (dataItem) => dataItem.id,
    );
    const autoGroupColumnDef = ref<AutoGroupColumnDef>({
      headerName: "Name",
      field: "name",
    });
    const detailRowHeight = ref(220);
    const detailCellRendererParams = ref<any>({
      detailGridOptions: {
        columnDefs: [{ field: "label" }, { field: "value" }],
        defaultColDef: { flex: 1 },
      },
      getDetailRowData: (params) => {
        params.successCallback(params.data.details || []);
      },
    } as IDetailCellRendererParams<any, any>);
    const isRowMaster = ref<IsRowMaster>((data) => !!data.details?.length);
    const rowData = ref<any[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      setTimeout(() => {
        params.api.getRowNode("1")?.setExpanded(true);
      }, 500);
    };

    return {
      gridApi,
      rowModelType,
      serverSideDatasource,
      columnDefs,
      defaultColDef,
      isServerSideGroup,
      getServerSideGroupKey,
      autoGroupColumnDef,
      detailRowHeight,
      detailCellRendererParams,
      isRowMaster,
      rowData,
      onGridReady,
    };
  },
});

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

[Live example: Tree Data with Master Detail](https://www.ag-grid.com/examples/server-side-model-master-detail/tree-data-master-detail/vue3)

## Detail Row Height

The height of detail rows can be configured in one of the following ways:

1. Using the `detailRowHeight` grid option property to set a fixed height for each detail row.
2. Using the `getRowHeight()` grid option callback to explicitly set height for each row individually. This callback will need to work out the pixel height of each detail row.
3. Using the `detailRowAutoHeight=true` property to let the grid automatically size the detail rows / grids to fit their rows.

The following snippets compares these approaches:

Option 1 - fixed detail row height, sets height for all details rows

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

this.detailRowHeight = 500;
```

Option 2 - dynamic detail row height, dynamically sets height for all rows

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

this.getRowHeight = params => {
    const isDetailRow = params.node.detail;

    // note that this callback gets called for all rows, not just the detail row
    if (isDetailRow) {
        // dynamically calculate detail row height
        return params.data.children.length * 50;
    }
    // for all non-detail rows, return 25, the default row height
    return 25;
};
```

Option 3 - use autoHeight

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

this.detailRowAutoHeight = true;
```

> **Note**
>
> Purging the cache and dynamic row heights do not work together for the Server-Side Row Model. If you are using dynamic row height, ensure `maxBlocksInCache` is not set.

### Example Using Callback getRowHeight()

The following example explicitly sets detail row heights based on the number of detail rows. Note the following:

- **getRowHeight()** - is implemented to size detail rows according to the number of records.
- **node.detail** - is used to identify 'detail' row nodes.

#### Dynamic Detail Row Height

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgGridVue } from "ag-grid-vue3";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetDetailRowDataParams,
  GetRowHeight,
  GridApi,
  GridOptions,
  GridReadyEvent,
  IDetailCellRendererParams,
  IServerSideDatasource,
  ModuleRegistry,
  RenderApiModule,
  RowApiModule,
  RowHeightParams,
  RowModelType,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  MasterDetailModule,
  ServerSideRowModelModule,
} from "ag-grid-enterprise";
import { FakeServer } from "./fakeServer";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

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

function getServerSideDatasource(server: any): IServerSideDatasource {
  return {
    getRows: (params) => {
      console.log("[Datasource] - rows requested by grid: ", params.request);
      const response = server.getData(params.request);
      // adding delay to simulate real server call
      setTimeout(() => {
        if (response.success) {
          // call the success callback
          params.success({
            rowData: response.rows,
            rowCount: response.lastRow,
          });
        } else {
          // inform the grid request failed
          params.fail();
        }
      }, 200);
    },
  };
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="height: 100%; box-sizing: border-box">
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :columnDefs="columnDefs"
        :defaultColDef="defaultColDef"
        :rowModelType="rowModelType"
        :masterDetail="true"
        :detailCellRendererParams="detailCellRendererParams"
        :getRowHeight="getRowHeight"
        :rowData="rowData"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      // group cell renderer needed for expand / collapse icons
      {
        field: "accountId",
        maxWidth: 200,
        cellRenderer: "agGroupCellRenderer",
      },
      { field: "name" },
      { field: "country" },
      { field: "calls" },
      { field: "totalDuration" },
    ]);
    const defaultColDef = ref<ColDef>({
      flex: 1,
    });
    const rowModelType = ref<RowModelType>("serverSide");
    const detailCellRendererParams = ref<any>({
      detailGridOptions: {
        columnDefs: [
          { field: "callId" },
          { field: "direction" },
          { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
          { field: "switchCode" },
          { field: "number" },
        ],
        domLayout: "autoHeight",
        defaultColDef: {
          flex: 1,
        },
      },
      getDetailRowData: (params: GetDetailRowDataParams) => {
        // supply details records to detail cell renderer (i.e. detail grid)
        params.successCallback(params.data.callRecords);
      },
    } as IDetailCellRendererParams<IAccount, ICallRecord>);
    const getRowHeight = ref<GetRowHeight>((params: RowHeightParams) => {
      if (params.node && params.node.detail) {
        const offset = 60;
        const sizes = params.api.getSizesForCurrentTheme() || {};
        const allDetailRowHeight =
          params.data.callRecords.length * sizes.rowHeight;
        return allDetailRowHeight + (sizes.headerHeight || 0) + offset;
      }
    });
    const rowData = ref<any[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      setTimeout(() => {
        // expand some master row
        const someRow = params.api.getRowNode("1");
        if (someRow) {
          someRow.setExpanded(true);
        }
      }, 1000);

      const updateData = (data) => {
        // setup the fake server with entire dataset
        const fakeServer = new FakeServer(data);
        // create datasource with a reference to the fake server
        const datasource = getServerSideDatasource(fakeServer);
        // register the datasource with the grid
        params.api!.setGridOption("serverSideDatasource", datasource);
      };

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

    return {
      gridApi,
      columnDefs,
      defaultColDef,
      rowModelType,
      detailCellRendererParams,
      getRowHeight,
      rowData,
      onGridReady,
    };
  },
});

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

[Live example: Dynamic Detail Row Height](https://www.ag-grid.com/examples/server-side-model-master-detail/dynamic-detail-row-height/vue3)

See [Master Detail Dynamic Height](https://www.ag-grid.com/vue-data-grid/master-detail-height/#dynamic-height) for more details.

### Example Using Property autoHeight

The following example gets the grid to auto-size all details sections to fit their rows. This is done by setting `masterGridOptions.detailRowAutoHeight = true`.

#### Auto Detail Row Height

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

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

function getServerSideDatasource(server: any): IServerSideDatasource {
  return {
    getRows: (params) => {
      console.log("[Datasource] - rows requested by grid: ", params.request);
      const response = server.getData(params.request);
      // adding delay to simulate real server call
      setTimeout(() => {
        if (response.success) {
          // call the success callback
          params.success({
            rowData: response.rows,
            rowCount: response.lastRow,
          });
        } else {
          // inform the grid request failed
          params.fail();
        }
      }, 200);
    },
  };
}

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="height: 100%; box-sizing: border-box">
      <ag-grid-vue
        style="width: 100%; height: 100%;"
        @grid-ready="onGridReady"
        :columnDefs="columnDefs"
        :defaultColDef="defaultColDef"
        :rowModelType="rowModelType"
        :masterDetail="true"
        :detailRowAutoHeight="true"
        :detailCellRendererParams="detailCellRendererParams"
        :rowData="rowData"></ag-grid-vue>
      </div>
        </div>
    `,
  components: {
    "ag-grid-vue": AgGridVue,
  },
  setup(props) {
    const gridApi = shallowRef<GridApi | null>(null);
    const columnDefs = ref<ColDef[]>([
      // group cell renderer needed for expand / collapse icons
      {
        field: "accountId",
        maxWidth: 200,
        cellRenderer: "agGroupCellRenderer",
      },
      { field: "name" },
      { field: "country" },
      { field: "calls" },
      { field: "totalDuration" },
    ]);
    const defaultColDef = ref<ColDef>({
      flex: 1,
    });
    const rowModelType = ref<RowModelType>("serverSide");
    const detailCellRendererParams = ref<any>({
      detailGridOptions: {
        columnDefs: [
          { field: "callId" },
          { field: "direction" },
          { field: "duration", valueFormatter: "x.toLocaleString() + 's'" },
          { field: "switchCode" },
          { field: "number" },
        ],
        defaultColDef: {
          flex: 1,
        },
      },
      getDetailRowData: (params) => {
        // supply details records to detail cell renderer (i.e. detail grid)
        params.successCallback(params.data.callRecords);
      },
    } as IDetailCellRendererParams<IAccount, ICallRecord>);
    const rowData = ref<any[]>(null);

    const onGridReady = (params: GridReadyEvent) => {
      gridApi.value = params.api;

      setTimeout(() => {
        // expand some master row
        const someRow = params.api.getRowNode("1");
        if (someRow) {
          someRow.setExpanded(true);
        }
      }, 1000);

      const updateData = (data) => {
        // setup the fake server with entire dataset
        const fakeServer = new FakeServer(data);
        // create datasource with a reference to the fake server
        const datasource = getServerSideDatasource(fakeServer);
        // register the datasource with the grid
        params.api!.setGridOption("serverSideDatasource", datasource);
      };

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

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

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

[Live example: Auto Detail Row Height](https://www.ag-grid.com/examples/server-side-model-master-detail/auto-detail-row-height/vue3)

See [Master Detail Auto Height](https://www.ag-grid.com/vue-data-grid/master-detail-height/#auto-height) for more details.

## Lazy Loading Detail Rows

In the examples above, the data for the detail grid was returned with the master row. However it is also possible to lazy-load data for the detail row, see: [Providing Rows](https://www.ag-grid.com/vue-data-grid/master-detail-grids/#providing-rows).

However note that detail rows will be purged once the master row is closed, or if the detail row leaves the viewport through scrolling. In both cases data will need to be fetched again.
