---
title: "PDF Export - Master Detail"
enterprise: true
framework: javascript
version: "36.1.0"
---

# PDF Export - Master Detail

By default, exporting a Master Detail grid includes the master rows but not their detail grids. Use `getCustomContentBelowRow` to return a PDF representation of the detail data that should be inserted below each master row.

## Exporting Master And Detail Rows

`getCustomContentBelowRow` receives the master row node and returns `PdfCell[][]`. Build these rows from the detail data stored on the master record:

```js
const gridOptions = {
    defaultPdfExportParams: {
        getCustomContentBelowRow: (params) => {
            const records = params.node.data.callRecords;

            return [
                [
                    { data: { value: 'Call ID' } },
                    { data: { value: 'Direction' } },
                    { data: { value: 'Number' } },
                    { data: { value: 'Duration / Switch Code' } },
                ],
                ...records.map((record) => [
                    { data: { value: String(record.callId) } },
                    { data: { value: record.direction } },
                    { data: { value: record.number } },
                    { data: { value: `${record.duration}s / ${record.switchCode}` } },
                ]),
            ];
        },
    },

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

This approach does not create or export a separate detail-grid instance. It works whether the detail row is expanded or collapsed and avoids creating every detail grid during export.

Detail-grid behaviour is not applied automatically. If `detailGridOptions` contains Value Getters, Value Formatters, sorting or filtering that should be represented in the PDF, apply the equivalent logic while building the custom rows.

> **Note**
>
> Custom content uses the master table's exported column layout. Build a compact detail representation that fits the number and widths of the exported master columns.

The example below exports a heading, column headers and all call records below each master account row. The duration formatting used by the detail grid is reproduced when the PDF content is created.

#### PDF Export - Master Detail

```ts
import {
  ClientSideRowModelModule,
  GridApi,
  GridOptions,
  IDetailCellRendererParams,
  ModuleRegistry,
  PdfCell,
  PdfCellStyle,
  ProcessRowGroupForExportParams,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ContextMenuModule,
  MasterDetailModule,
  PdfExportModule,
} from "ag-grid-enterprise";
import { IAccount, ICallRecord } from "./interfaces";

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

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

const detailHeadingStyle: PdfCellStyle = {
  backgroundColor: "#d9eaf7",
  color: "#19486a",
  fontWeight: "bold",
  padding: 6,
};

const detailHeaderStyle: PdfCellStyle = {
  backgroundColor: "#eef4f8",
  color: "#243746",
  fontWeight: "bold",
  padding: 5,
};

function getDetailRows(
  params: ProcessRowGroupForExportParams<IAccount>,
): PdfCell[][] {
  const account = params.node.data;
  if (!account) {
    return [];
  }

  const rows: PdfCell[][] = [
    [
      {
        data: { value: `Calls for ${account.name}` },
        mergeAcross: 3,
        style: detailHeadingStyle,
      },
    ],
    [
      { data: { value: "Call ID" }, style: detailHeaderStyle },
      { data: { value: "Direction" }, style: detailHeaderStyle },
      { data: { value: "Number" }, style: detailHeaderStyle },
      { data: { value: "Duration / Switch Code" }, style: detailHeaderStyle },
    ],
  ];

  for (const record of account.callRecords) {
    rows.push([
      { data: { value: String(record.callId) } },
      { data: { value: record.direction } },
      { data: { value: record.number } },
      { data: { value: `${record.duration}s / ${record.switchCode}` } },
    ]);
  }

  return rows;
}

let gridApi: GridApi<IAccount>;

const gridOptions: GridOptions<IAccount> = {
  columnDefs: [
    { field: "name", cellRenderer: "agGroupCellRenderer", minWidth: 180 },
    { field: "account" },
    { field: "calls" },
    { field: "minutes", valueFormatter: (params) => `${params.value}m` },
  ],
  defaultColDef: {
    flex: 1,
    minWidth: 110,
  },
  masterDetail: true,
  detailCellRendererParams: {
    detailGridOptions: {
      columnDefs: [
        { field: "callId" },
        { field: "direction" },
        { field: "number", minWidth: 150 },
        { field: "duration", valueFormatter: (params) => `${params.value}s` },
        { field: "switchCode", minWidth: 150 },
      ],
      defaultColDef: {
        flex: 1,
      },
    },
    getDetailRowData: (params) => {
      params.successCallback(params.data.callRecords);
    },
  } as IDetailCellRendererParams<IAccount, ICallRecord>,
  defaultPdfExportParams: {
    columnWidth: "auto",
    getCustomContentBelowRow: getDetailRows,
  },
};

function onBtExport() {
  gridApi.exportDataAsPdf();
}

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

fetch("https://www.ag-grid.com/example-assets/master-detail-data.json")
  .then((response) => response.json())
  .then((data: IAccount[]) => {
    gridApi.setGridOption("rowData", data.slice(0, 3));
  });

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

[Live example: PDF Export - Master Detail](https://www.ag-grid.com/examples/pdf-export-master-detail/pdf-export-master-detail/typescript/)
