Convert HTML to PDF with Node.js and Puppeteer
A practical workflow for rendering web pages as PDF documents.
Puppeteer renders a web page in Chromium and prints it to PDF, preserving selectable text and print CSS. This example accepts a URL, page dimensions and margins on the command line.
PDF page dimensions use physical units. Puppeteer has no PDF DPI setting; increasing the viewport size changes the page layout rather than the resolution of vector text. See Puppeteer’s PDF options.
Set up Puppeteer
First, we need to import Puppeteer and set up command line argument parsing with yargs. Puppeteer is used to control a headless Chrome or Chromium browser, and yargs helps in parsing command line arguments.
const puppeteer = require('puppeteer');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
Define page sizes
Next, we define standard page sizes in inches. These sizes will be used to set the dimensions of the PDF pages.
const pageSizes = {
A4: { width: 8.27, height: 11.69 },
Letter: { width: 8.5, height: 11 },
Legal: { width: 8.5, height: 14 },
Tabloid: { width: 11, height: 17 },
Executive: { width: 7.25, height: 10.5 },
A5: { width: 5.83, height: 8.27 },
A3: { width: 11.69, height: 16.54 }
};
Configure command line arguments
We use yargs to configure the command line arguments for our script. These arguments allow the user to specify various parameters such as the URL, output file path, page size, and margins.
const argv = yargs(hideBin(process.argv))
.option('url', { type: 'string', demandOption: true })
.option('output', { type: 'string', demandOption: true })
.option('pageSize', { choices: Object.keys(pageSizes) })
.option('width', { type: 'number', describe: 'Page width in inches' })
.option('height', { type: 'number', describe: 'Page height in inches' })
.option('scale', { type: 'number', default: 1 })
.option('background', { type: 'boolean', default: true })
.option('margin', { type: 'boolean', default: true })
.option('top', { type: 'number', default: 10 })
.option('right', { type: 'number', default: 10 })
.option('bottom', { type: 'number', default: 10 })
.option('left', { type: 'number', default: 10 })
.check(args => {
if (!['http:', 'https:', 'file:'].includes(new URL(args.url).protocol))
throw new Error('Use an HTTP, HTTPS or file URL.');
const custom = args.width !== undefined || args.height !== undefined;
if (custom && (args.pageSize || !(args.width > 0 && args.height > 0)
|| !Number.isFinite(args.width) || !Number.isFinite(args.height)))
throw new Error('Supply both positive dimensions, or a pageSize.');
if (!(args.scale >= 0.1 && args.scale <= 2))
throw new Error('Scale must be between 0.1 and 2.');
for (const side of ['top', 'right', 'bottom', 'left']) {
if (!Number.isFinite(args[side]) || args[side] < 0)
throw new Error('Margins must be nonnegative millimeters.');
}
return true;
})
.strict()
.help()
.parse();
const { width, height } = argv.width !== undefined
? { width: argv.width, height: argv.height }
: pageSizes[argv.pageSize || 'A4'];
Generate the PDF
We create an async function to generate the PDF using Puppeteer. This function launches a headless browser, navigates to the specified URL, and generates the PDF with the specified settings.
async function generatePDF() {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto(argv.url, { waitUntil: 'networkidle0', timeout: 60000 });
await page.pdf({
path: argv.output,
width: `${width}in`,
height: `${height}in`,
printBackground: argv.background,
scale: argv.scale,
margin: Object.fromEntries(
['top', 'right', 'bottom', 'left'].map(side =>
[side, `${argv.margin ? argv[side] : 0}mm`])
)
});
} finally {
await browser.close();
}
}
generatePDF().catch(err => {
console.error(err);
process.exitCode = 1;
});
Run the script
Install the dependencies with npm install puppeteer yargs, save the blocks above as pdfgen.cjs, then run:
node pdfgen.cjs --url https://example.com --output example.pdf --pageSize A4 --top 10 --right 10 --bottom 10 --left 10
This creates an A4 PDF with 10 mm margins. Use --width 8 --height 10 for custom dimensions, --no-margin for borderless output, or --no-background to omit background fills. Print CSS controls page breaks and content visibility.