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 Copy Link
getCustomContentBelowRow receives the master row node and returns PdfCell[][]. Build these rows from the detail data stored on the master record:
const 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}` } },
]),
];
},
};
<AgGridReact defaultPdfExportParams={defaultPdfExportParams} />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.
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.
"use client";
import React, {
useCallback,
useMemo,
useRef,
useState,
StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
ClientSideRowModelModule,
ColDef,
ColGroupDef,
GridApi,
GridOptions,
IDetailCellRendererParams,
ModuleRegistry,
PdfCell,
PdfCellStyle,
PdfExportParams,
ProcessRowGroupForExportParams,
enableDevValidations,
} from "ag-grid-community";
import {
ColumnMenuModule,
ContextMenuModule,
MasterDetailModule,
PdfExportModule,
} from "ag-grid-enterprise";
import { IAccount, ICallRecord } from "./interfaces";
import { useFetchJson } from "./useFetchJson";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
const modules = [
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,
};
const getDetailRows: (
params: ProcessRowGroupForExportParams<IAccount>,
) => PdfCell[][] = (params: ProcessRowGroupForExportParams<IAccount>) => {
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;
};
const GridExample = () => {
const gridRef = useRef<AgGridReact<IAccount>>(null);
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [columnDefs, setColumnDefs] = useState<ColDef[]>([
{ field: "name", cellRenderer: "agGroupCellRenderer", minWidth: 180 },
{ field: "account" },
{ field: "calls" },
{ field: "minutes", valueFormatter: (params) => `${params.value}m` },
]);
const defaultColDef = useMemo<ColDef>(() => {
return {
flex: 1,
minWidth: 110,
};
}, []);
const detailCellRendererParams = useMemo(() => {
return {
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>;
}, []);
const defaultPdfExportParams = useMemo<PdfExportParams>(() => {
return {
columnWidth: "auto",
getCustomContentBelowRow: getDetailRows,
};
}, []);
const { data, loading } = useFetchJson<IAccount>(
"https://www.ag-grid.com/example-assets/master-detail-data.json",
3,
);
const onBtExport = useCallback(() => {
gridRef.current!.api.exportDataAsPdf();
}, []);
return (
<AgGridProvider modules={modules}>
<div style={containerStyle}>
<div className="example-wrapper">
<div className="controls">
<button onClick={onBtExport}>Export to PDF</button>
</div>
<div style={gridStyle}>
<AgGridReact<IAccount>
ref={gridRef}
rowData={data}
loading={loading}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
masterDetail={true}
detailCellRendererParams={detailCellRendererParams}
defaultPdfExportParams={defaultPdfExportParams}
/>
</div>
</div>
</div>
</AgGridProvider>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<GridExample />
</StrictMode>,
);
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
.controls {
margin-bottom: 8px;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
export interface ICallRecord {
name: string;
callId: number;
duration: number;
switchCode: string;
direction: string;
number: string;
}
export interface IAccount {
name: string;
account: number;
calls: number;
minutes: number;
callRecords: ICallRecord[];
} import { useState, useEffect } from 'react';
/**
* Fetch example Json data
* Not recommended for production use!
*/
export const useFetchJson = <T,>(url:string, limit?: number) => {
const [data, setData] = useState<T[]>();
const [loading, setLoading] = useState(true);
useEffect(() => {
// StrictMode runs this effect twice: drop the superseded run's response rather than applying both.
let cancelled = false;
const fetchData = async () => {
setLoading(true);
// Note error handling is omitted here for brevity
const response = await fetch(url);
const json = await response.json();
const data = limit ? json.slice(0, limit) : json;
if (cancelled) {
return;
}
setData(data);
setLoading(false);
};
fetchData();
return () => {
cancelled = true;
};
}, [url, limit]);
return { data, loading };
}; API Copy Link
Export Options Copy Link
See below the functions on the PdfExportParams interface to customise exported grid values.
A callback function to return content to be inserted below a row in the export. |
PdfCell Copy Link
Properties available on the PdfCell interface.
The data that will be added to the cell. |
The number of cells to span across (1 means span 2 columns). |
Optional styling for the cell.
|
PdfCellData Copy Link
Properties available on the PdfCellData interface.
The value of the cell. |
External URI opened when the exported cell text is selected. |
Image rendered alongside the cell value. |
ProcessRowGroupForExportParams Copy Link
Properties available on the ProcessRowGroupForExportParams<TData = any, TContext = any> interface.
Row node. |
The grid column |
The grid api. |
Application context as set on gridOptions.context. |