How to generate dashboard and report PDFs using Puppeteer

contents
 PDFs and Puppeteer

Export a PDF they say...it'll be simple they say...

"Our users want to share their reports, can you just add an export button? Ideally by tomorrow" - Project Managers

Many SaaS companies have user dashboards and metrics. It's where users actually understand how the tool or service helps them.

But, what about if they want to share those numbers around?

Friends don't let friends send screengrabs
Friends don't let friends send screengrabs

You don't want them to hit Print to PDF or just send a screengrab since that gives unprofessional results. So, you might look at a method like jsPDF. It typically runs on the client side, meaning it clogs up the visitor's device and gives inconsistent results.

Automating the process through Puppeteer is much more reliable. It gives you advanced abilities such as inserting style tags and handling log ins.

We'll share with you a puppeteer pdf generator code snippet that can run in your own Puppeteer instance, or with our hosted browsers.

How to automate PDF generation with puppeteer-core

Let's demonstrate an export with our own account dashboard. The script will:

  • Define our variables and endpoint
  • Set the viewport
  • Wait for network traffic for idle
  • Submit login credentials
  • Remove unwanted elements
  • Prevent CSS shifts and font swaps
  • Print the page with a background

You can run this script with the default puppeteer library, but we would always recommend using puppeteer-core. Puppeteer includes a bundled Chrome instance, which is suitable for running locally but is bad for scaling on cloud hosting. Puppeteer-core only contains the library so that you can run the browsers on a separate server (or use ours), similar to how you'd host an app and database on separate servers.

You can then make a simple NodeJS app to schedule that task and email the PDF. Or you can send these PDFs via your current Email Marketing tools.

Here's the PDF we'll be generating
Here's the PDF we'll be generating

Creating our script

Here is the dashboard we'll be exporting. We'll be running the scripts using Browserless, which requires an API, but they will also work in your own puppeteer-core deployments.

The dashboard this script will export
The dashboard this script will export

To extract this dashboard, we can use the /function API to run the script below, where "token" is your API key from Browserless. The /function API is a quick way to run commands without having to install any libraries on your end:


const puppeteer = require('puppeteer-core');

(async() => {
    const token = "YOUR_API_KEY";
    const email = "YOUR_LOGIN_EMAIL";
    const password = "YOUR_LOGIN_PASSWORD";

    const browser = await puppeteer.connect({ browserWSEndpoint: 'wss://chrome.browserless.io?token='+token });
    const page = await browser.newPage();
    await page.setViewport({width:800,height:1020});
    await page.goto('https://cloud.browserless.io/account/',{ waitUntil: 'networkidle0'});
    await page.type('#login-email', email, {delay: 10});
    await page.type('#login-password', password, {delay: 10});
    await page.click('div.css-vxcmzt > div > button > span');
    await page.waitForSelector('.chartjs-render-monitor');
    await page.evaluate(() => {
        var leftpanel = document.querySelector(".sticky_nav__3r2Ep");
        leftpanel.parentNode.removeChild(leftpanel);
        const date = new Date();
        document.querySelector('.text-white.mb-0').innerHTML="Sessions on "+date;
        document.querySelector('#app > div > div > div > .col-8').classList.add("col-12");
        document.querySelector('#app > div > div > div > .col-12').classList.remove("col-8");
    })
    await page.emulateMediaType('screen');
    return page.pdf({path:"dashboard.pdf",printBackground:true});
})();

It's a simple script, we basically access the dashboard URL, login with our credentials, and click on the sign in button. Once the dashboard is loaded, we generate the PDF.

First we import the puppeteer-core library, which is lightweight since you'll be connecting to a remote or existing chrome, and doesn't come with browser binaries.


const puppeteer = require('puppeteer-core');

We'll wrap all our code inside an async IIFE so that our code executes off the bat. Then we define our local variables, such as our API KEY, email, and password. The best practice here is to use process environments, but we'll keep it simple for now.


(async() => {
    const token = "YOUR_API_KEY";
    const email = "YOUR_LOGIN_EMAIL";
    const password = "YOUR_LOGIN_PASSWORD";

Now let's connect to the browserless WS endpoint by providing our API KEY and create a new browser and page to start automating.


const browser = await puppeteer.connect({ browserWSEndpoint: 'wss://chrome.browserless.io?token='+token });
const page = await browser.newPage();

Once that's done, we'll set the desired viewport


await page.setViewport({
    width: 1920,
    height: 1080
})

We then go to the browserless account page and wait for the network traffic to settle down so that the email and password selectors are actually loaded.


await page.goto('https://cloud.browserless.io/account/',
{ 
	waitUntil: 'networkidle0'
});

We enter our credentials and click on the submit button - you can use environment variables for the password here.


await page.type('#login-email', 'YOUR_EMAIL', {delay: 50});
await page.type('#login-password', 'YOUR_PASSWORD', {delay: 50});
await page.click('div.css-vxcmzt > div > button > span');

Once we've clicked log in, we want to wait to make sure the page is fully loaded by checking that the graph has been rendered.


  await page.waitForSelector('.chartjs-render-monitor');

Now we want to modify the page before generating the PDF. We can do so inside the page.evaluate() method. We are fetching the left panel navigation menu and removing it. Then we are finding the main panel that has the content that we want, we'll remove the .col-8 class and add the .col-12 class so that it is fullscreen. You can feel free to modify the UI of your dashboard in this section, such as removing unwanted sections or adding new graphic elements by injecting html+css that you may want to show in the PDF.


await page.evaluate(() => {
  var leftpanel = document.querySelector(".sticky_nav__3r2Ep");
  leftpanel.parentNode.removeChild(leftpanel); //removing the left panel
  const date = new Date();
  document.querySelector('.text-white.mb-0').innerHTML="Sessions on "+date; //adding the date in the title
  document.querySelector('#app > div > div > div > .col-8').classList.add("col-12"); //adding this class to be fullscreen
  document.querySelector('#app > div > div > div > .col-12').classList.remove("col-8"); //removing this class to overwrite the container size.
})

After all your modifications are done, feel free to generate the pdf. It is common that CSS defaults to print CSS styles (in order to save ink when printing) so you can add these two lines of code to make the CSS look more like a user would usually look at it. Otherwise the CSS could shift and look weird.


await page.emulateMediaType('screen'); //will help not to render print css
return page.pdf({printBackground:true}); //will render backgrounds of your page

In some cases pages are rendering their fonts with web fonts, so if your page looks weird even after adding these two lines of code, it could be that the web fonts aren't loading properly because the page detects you're running chrome headless, and hence doesn't see the need to render any fonts at all. To overcome this, you can either run the session headful or set the user agent manually as so:


await page.setUserAgent(
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36'
);

Note: for more info on reliably printing fonts, check out this article.

Here's an example of why setting these last two lines of code are relevant:

Page with only page.pdf(); looks like this:

Page with only page.pdf(); looks like this

Page with print background looks like this:

Page with print background

The first graph isn't rendering properly, so we'll use emulateMediaType set to screen to create our final result:

Use emulateMediaType set

Now you can take care of sending this PDF through your marketing platforms, have fun!

Run your Puppeteer scripts with our browsers

If you need to scale our your exports, then try Browserless. We have a fleet of managed headless browsers, ready for use with any automation.

Try it free for 7 days

If you like this "Puppeteer PDF generator" article, you can check out how our clients use Browserless for different use cases:

Share this article

Ready to try the benefits of Browserless?