Exporting AG Grid to PDF with pdfMake

  |   Tutorial
Feature image showing AG Grid export to PDF

In this blog, you will learn how to export AG Grid to a PDF using pdfMake, a document generation library for JavaScript. We'll demonstrate how to export your grid, including user configurations, such as filters, row groups, and pivots, as well as styles such as row & border colours.

Use the sidebar and column headers to configure the grid and then click "Export to PDF" to download the grid as a PDF:

This blog is focused on JavaScript, but we have examples showing how to export AG Grid to PDF in React, Angular and Vue, too. You can view or download the code for the demo in each of the major frameworks here:

Exporting a Simple Grid

It's very simple to export an AG Grid data grid to PDF by using pdfMake's table functionality, which takes our data (e.g. our headers, rows and columns) and creates a table within a PDF document.

This section covers how to extract the headers, rows and columns from your grid and pass them to pdfMake via a PDF Document Definiton Object.

Headers

First, let's define a getHeaderToExport function to extract the column header names, and any sorting applied by the user, from the grid header:

/**
 * This function iterates over all of the columns to create a row of header cells
 */

const getHeaderToExport = (gridApi) => {
  const columns = gridApi.getAllDisplayedColumns();

  return columns.map((column) => {
    const { field } = column.getColDef();
    const sort = column.getSort();
    const headerNameUppercase =
      field[0].toUpperCase() + field.slice(1);
    const headerCell = {
      text: headerNameUppercase + (sort ? ` (${sort})` : ''),
    };
    return headerCell;
  });
};

In this function, we iterate over the array of columns returned from the getAllDisplayedColumns API, and extract the field and sort properties from the column definition using getColDef and getSort.

We then parse the field property to define the text for each header cell by capitalising the first letter and including any sorting applied by the user. The result would look something like this:

[{ text: "ColumnA (asc)" }, { text: "ColumnB" }, ...]

Rows

Now we have an array of column header cells, we need a similar function to extract the cells from the grid rows - getRowsToExport:

/**
 * This function iterates over all of the rows and columns to create
 * a matrix of cells
 */
const getRowsToExport = (gridApi) => {
  const columns = gridApi.getAllDisplayedColumns();

  const getCellToExport = (column, node) => ({
    text: gridApi.getValue(column, node) ?? '',
  });

  const rowsToExport = [];
  gridApi.forEachNodeAfterFilterAndSort((node) => {
    const rowToExport = columns.map((column) =>
      getCellToExport(column, node)
    );
    rowsToExport.push(rowToExport);
  });

  return rowsToExport;
};

First, we iterate over every row with the forEachNodeAfterFilterAndSort API to extract each row node, whilst taking into account any filters or sorting that may have been applied by the user. Then, as we did in the last step, we loop through each column returned by getAllDisplayedColumns, this time extracting the cell value using the getValue API.

PDF Document Definition Object

With our data ready to export, all we need to do this pull this together into a PDF Document Definition Object which defines how our table is created by pdfMake.

To do this, we've defined the getDocument function, which takes our data and returns the PDF Document Definition Object

/** 
* This function returns a PDF document definition object - the input for pdfMake.
*/
const getDocument = (gridApi) => {
  const columns = gridApi.getAllDisplayedColumns();

  const headerRow = getHeaderToExport(gridApi);
  const rows = getRowsToExport(gridApi);

  return {
    pageOrientation: 'landscape', // can also be 'portrait'
    content: [
      {
        table: {
          // the number of header rows
          headerRows: 1,

          // the width of each column, can be an array of widths
          widths: `${100 / columns.length}%`,

          // all the rows to display, including the header rows
          body: [headerRow, ...rows],

          // Header row is 40px, other rows are 15px
          heights: (rowIndex) => (rowIndex === 0 ? 40 : 15),
        },
      },
    ],
  };
};

Inside this object, we're configuring the orientation of the page, the number of header rows, width of the columns, the body of the table (e.g. our headers and rows), and the height of the rows - everything pdfMake needs to create a table.

Creating & Downloading PDF

Finally, it's as simple as defining an export function to call within your code, and passing the PDF Document Definition Object to pdfMake:

export const exportToPDF = (gridApi) => {
  const doc = getDocument(gridApi);
  pdfMake.createPdf(doc).download();
};

Example pdfMake Document Definition Object

Here's an example of a PDF document definition object which would define a simple grid:

{
  pageOrientation: 'landscape', // can also be 'portrait'
  content: [
    {
      table: {
        // the number of header rows
        headerRows: 1,

        // the width of each column, can be an array of widths
        widths: `25%`,

        // all the rows to display, including the header rows
        body: [
          // As we have defined headerRows as '1', only the first row
          // is used as the header
          [
            { text: 'Athlete' },
            { text: 'Country' },
            { text: 'Age' },
            { text: 'Sport' },
          ],

          // All of the following rows are the body rows of your table
          [
            { text: 'Natalie Coughlin' },
            { text: 'United States' },
            { text: '25' },
            { text: 'Swimming' },
          ],

          [
            { text: 'Kirsty Coventry' },
            { text: 'Zimbabwe' },
            { text: '24' },
            { text: 'Swimming' },
          ]
        ],

        // Header row is 40px, other rows are 15px
        heights: (rowIndex) => (rowIndex === 0 ? 40 : 15),
      },
    },
  ],
}

This would result in a PDF of this grid being generated and exported by pdfMake:

Athlete Country Age Sport
Natalie Coughlin United States 25 Swimming
Kirsty Coventry Zimbabe 24 Swimming

Styling Your Grid

Using the code snippets provided already, AG Grid can be exported to PDF without any styling. The pdfMake library supports a variety of styling and layout options for exported grids which can be applied by adjusting the PDF Document Definition Object.

To adjust the layout of the exported grid, you can define your own table layout.

First, define the variables you will need to assign colours to your rows and the borders of your PDF:

// Row colors
const HEADER_ROW_COLOR = '#f8f8f8';
const EVEN_ROW_COLOR = '#fcfcfc';
const ODD_ROW_COLOR = '#fff';

const PDF_INNER_BORDER_COLOR = '#dde2eb';
const PDF_OUTER_BORDER_COLOR = '#babfc7';

Then, create a table layout passing in those colours, along with any spacing you require in your exported grid:

const createLayout = (numberOfHeaderRows) => ({
  fillColor: (rowIndex) => {
    if (rowIndex < numberOfHeaderRows) {
      return HEADER_ROW_COLOR;
    }
    return rowIndex % 2 === 0 ? EVEN_ROW_COLOR : ODD_ROW_COLOR;
  },
  //vLineHeight not used here.
  vLineWidth: (rowIndex, node) =>
    rowIndex === 0 || rowIndex === node.table.widths.length ? 1 : 0,
  hLineColor: (rowIndex, node) =>
    rowIndex === 0 || rowIndex === node.table.body.length
      ? PDF_OUTER_BORDER_COLOR
      : PDF_INNER_BORDER_COLOR,
  vLineColor: (rowIndex, node) =>
    rowIndex === 0 || rowIndex === node.table.widths.length
      ? PDF_OUTER_BORDER_COLOR
      : PDF_INNER_BORDER_COLOR,
});

Then add this function, along with an array to define the PDF pageMargins, to your getDocument function:

const getDocument = (gridApi) => {
  const columns = gridApi.getAllDisplayedColumns();

  const headerRow = getHeaderToExport(gridApi);
  const rows = getRowsToExport(gridApi);

  return {
    pageOrientation: 'landscape', // can also be 'portrait'
    content: [
      {
        table: {
          // the number of header rows
          headerRows: 1,

          // the width of each column, can be an array of widths
          widths: `${100 / columns.length}%`,

          // all the rows to display, including the header rows
          body: [headerRow, ...rows],

          // Header row is 40px, other rows are 15px
          heights: (rowIndex) => (rowIndex === 0 ? 40 : 15),
        },
        layout: createLayout(1),
      },
    ],
    pageMargins: [10, 10, 10, 10],
  };
};

Styling Headers

To differentiate the header cells from the rows, we can add styling within the headerCell definition in the getHeaderToExport function.

Replace the previously defined function with this snippet which adds bold and margin properties to the headerCell:

const getHeaderToExport = (gridApi) => {
  const columns = gridApi.getAllDisplayedColumns();

  return columns.map((column) => {
    const { field, sort } = column.getColDef();
    const headerNameUppercase =
      field[0].toUpperCase() + field.slice(1);
    const headerCell = {
      text: headerNameUppercase + (sort ? ` (${sort})` : ''),

      // styles
      bold: true,
      margin: [0, 12, 0, 0],
    };
    return headerCell;
  });
};

Applying Row Styles

We can access the row cellStyle property using the AG Grid column reference methods and then pass those styles to the pdfMake library.

To do this, replace the previously defined getRowsToExport function with this snippet, which adds cell Styles to the rowToExport object by calling getColDef().cellStyle:

const getRowsToExport = (gridApi) => {
  const columns = gridApi.getAllDisplayedColumns();

  const getCellToExport = (column, node) => ({
    text: gridApi.getValue(column, node) ?? '',
    // styles
    ...column.getColDef().cellStyle,
  });

  const rowsToExport = [];
  gridApi.forEachNodeAfterFilterAndSort((node) => {
    const rowToExport = columns.map((column) => getCellToExport(column, node));
    rowsToExport.push(rowToExport);
  });

  return rowsToExport;
};

Other styles which are supported by pdfMake can be applied to your exported grid similarly.

Exporting Row Groups

Row Groups can be exported by adapting the getHeaderToExport function.

The only change we need to make here is overriding the field property with headername as the 'Group' column is generated by AG Grid so it doesn't have a field!

/**
 * This function iterates over all of the columns to create a row of header cells and supports row grouping
 */

const getHeaderToExport = (gridApi) => {
  const columns = gridApi.getAllDisplayedColumns();

  return columns.map((column) => {
    const { field, sort } = column.getColDef();
    // Enables export when row grouping
    const headerName = column.getColDef().headerName ?? field;
    const headerNameUppercase =
      headerName[0].toUpperCase() + headerName.slice(1);
    const headerCell = {
      text: headerNameUppercase + (sort ? ` (${sort})` : ''),

      // styles
      bold: true,
      margin: [0, 12, 0, 0],
    };
    return headerCell;
  });
};

Exporting a Pivoted Grid

Pivoted grids can also be exported. First, make sure you've followed the steps above for Row Groups! Then, we need to adapt our getRowsToExport function:

/**
 * This function iterates over all of the rows and columns to create
 * a matrix of cells when pivoting is enabled
 */

const getRowsToExportPivot = (gridApi) => {
  const columns = gridApi.getAllDisplayedColumns();

  const getCellToExport = (column, node) => ({
    text: gridApi.getValue(column, node) ?? '',
    // styles
    ...column.getColDef().cellStyle,
  });

  const rowsToExport = [];
  gridApi.forEachNodeAfterFilterAndSort((node) => {
    if (node.aggData) {
      const rowToExport = columns.map((column) =>
        getCellToExport(column, node)
      );
      rowsToExport.push(rowToExport);
    }
  });

  return rowsToExport;
};

The original getRowsToExport function exports all the rows, including those which are not displayed in pivot mode. Pivot mode only displays rows with aggregated data so we only want to export these. To achieve this, we have added an if statement to check that the node contains aggregated data by calling node.aggData. Only those that do are then passed to the getCellToExport function.

What's next?

We hope this article helps you start exporting AG Grid to PDF!

If you need a different method to export your AG Grid, don't forget about the in-built functionality to export AG Grid to Excel and CSV.

If you would like to try out AG Grid check out our getting started guides (JS / React / Angular / Vue)

Happy coding!

Read more posts about...