Convert HTML to PDF in JavaScript: Review of the best libraries
To convert HTML to PDF in JavaScript, first choose where the conversion should run. Use jsPDF or html2pdf.js for simple client-side documents; use Puppeteer or Playwright when a server must render modern CSS, multipage layouts, and JavaScript before generating a PDF.
By the end of this guide you should have a good idea of which option would work best for your use case. At the bottom of the page there’s also a table that recaps all of my findings.
tl;dr Browser for simple documents, server for browser-faithful output
For server-side output, Puppeteer and Playwright use browser print rendering for stronger CSS fidelity, JavaScript execution, selectable text, and multipage output. If your project is specifically Node/npm, use the Node.js HTML-to-PDF guide for implementation-focused coverage.
For browser-side output, jsPDF and html2pdf.js keep document data on the user’s device and avoid server infrastructure, but their canvas-based rendering limits selectable text and can be awkward for long documents. The React HTML-to-PDF guide covers the same client-side tradeoffs for React applications.
Puppeteer
Puppeteer is a Javascript library which allows you to control a headless Chrome (or Firefox) instance using a nice API.
It essentially allows you to automate anything that you can do “manually” in the browser. And one thing you can always do manually in a browser is print HTML webpages to a PDF file. With Puppeteer we can automate this using Javascript.
Installing it is straightforward with npm:
npm install puppeteer
When installing it, Puppeteer will also download a recent version of Chrome.
Here’s how to generate a PDF from a URL:
const puppeteer = require('puppeteer');
async function generatePDF() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Navigate to URL
await page.goto('https://transformy.io/guides/', {
waitUntil: 'networkidle2'
});
// Generate PDF
await page.pdf({
path: 'transformy_guides.pdf',
format: 'A4',
printBackground: true
});
await browser.close();
}
generatePDF();
You can also generate PDFs from HTML strings:
async function generatePDFFromHTML() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const html = ''; // add your HTML here
await page.setContent(html);
await page.pdf({
path: 'invoice.pdf',
format: 'A4',
});
await browser.close();
}
generatePDFFromHTML();
Puppeteer really shines with complicated HTML or Javascript rich pages. One thing that you can do is wait until a specific part of the page has finished loading before you generate the PDF.
// Wait for JS content to finish loading
await page.waitForSelector('.charts-container');
await page.pdf({ path: 'charts-report.pdf' });
This is just one simple example but it shows how far you can push things with Puppeteer and the headless browser powering it. It does come with a cost though: it’s slower than many other solutions and scaling this to support large numbers of (paralel) PDF conversions comes with a lot of hassle.
If you need browser-faithful output without deploying and scaling browser workers yourself, a managed HTML-to-PDF API moves that operational work out of your application.
jsPDF
jsPDF is a client-side PDF generation library. It’s not specifically developed for HTML to PDF conversion, but, together with html2canvas, it can be used to do exactly that.
Here’s how to install both libraries:
npm install jspdf
npm install html2canvas
There are essentially two steps to converting HTML to PDF with this setup. First, you convert the html file or string to a canvas using html2canvas. Next you add that canvas to the PDF and you specify the position.
Here’s how:
import { jsPDF } from "jspdf";
import html2canvas from "html2canvas";
// Get the element you want to convert
const element = document.getElementById("invoice-container");
html2canvas(element).then((canvas) => {
const imgData = canvas.toDataURL("image/png");
const pdf = new jsPDF();
const imgWidth = 210; // A4 width in mm
const pageHeight = 297; // A4 height in mm
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = 0;
pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
pdf.save("invoice.pdf");
});
It’s quite straightforward, but if the generated canvas doesn’t fit the size of your PDF you need to start fiddling until it does.
When you want to convert to a multi-page PDF, you need to calculate where the different pages start/stop:
// Add new pages if content is longer than one page
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
The fact that jsPDF can run in the browser is really conveniant and it makes it a good option if that’s something you need. But, the process boils down to taking a screenshot of your content and printing it and as you might image, that can come with hickups.
html2pdf.js
html2pdf.js is a wrapper library that combines html2canvas and jsPDF, making both installation and usage smoother. Whereas jsPDF is a general PDF generation tool, this one focuses on HTML to PDF conversion only.
Installation is just one line:
npm install html2pdf.js
The great thing about it is the API. It doesn’t get much easier than this:
import html2pdf from 'html2pdf.js';
var element = document.getElementById('content-container');
html2pdf(element);
You can also configure it with a bunch of options:
const opt = {
margin: 1,
filename: 'transformy-innvoice.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2 },
jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
};
html2pdf().set(opt).from(element).save();
If you need something that runs in the browser, this is a nicer option than directly using jsPDF and html2canvas. But it still comes with the same cons and the number of real world use cases for this is limited.
node-wkhtmltopdf
wkhtmltopdf is a very popular HTML to PDF command line tool. There’s an equally popular Python wrapper for it, and there is one for Javascript too.
It doesn’t get talked about much in the Javascript community though. That’s likely because wkhtmltopdf isn’t actively maintained anymore. The command line tool is built on top of the Qt Webkit rendering engine which also isn’t actively maintained.
The Javascript wrapper node-wkhtmltopdf hasn’t been updated in a few years. Combined with its archived rendering dependency, that makes it a legacy choice for existing deployments rather than a recommendation for new work.
Before you can use the package, you first need to install wkhtmltopdf on your system. I’ve written about how to do that in detail in my wkhtmltopdf tutorial. Once you’ve done that, you can install the node wrapper:
npm install wkhtmltopdf
The wrapper comes with an easy to use API: wkhtmltopdf(source, [options], [callback]).
First add the library:
var wkhtmltopdf = require('wkhtmltopdf');
Here’s how you can generate a PDF from a URL:
wkhtmltopdf('http://transformy.io/guides/', { pageSize: 'letter', output: 'transformy.pdf' })
Or from an HTML string:
wkhtmltopdf('<h1>Hello transformy</h1>', { pageSize: 'letter', output: 'transformy.pdf' })
node-wkhtmltopdf also accepts all the additional configuration options that the command line tool does which allow for a lot of customization.
Since the underlying library and rendering engine are no longer maintaned, this won’t work very well with the latest CSS and Javascript. Keep it only for an existing simple workload that cannot migrate yet.
Conclusion
Here’s a comparison table with a recap of all the libraries that I reviewed in this article.
| Option | Selectable text | Multipage output | JavaScript execution | CSS fidelity | Privacy | Operational cost |
|---|---|---|---|---|---|---|
| jsPDF + html2canvas | No; canvas image | Manual page splitting | Captures the current DOM | Limited by html2canvas | Stays in the browser | Low |
| html2pdf.js | No; canvas image | Page-break controls, with canvas size limits | Captures the current DOM | Limited by html2canvas | Stays in the browser | Low |
| Puppeteer | Yes | Native browser print output | Yes | High | Runs on your server | High |
| Playwright | Yes | Native browser print output | Yes | High | Runs on your server | High |
| node-wkhtmltopdf | Yes | Built in | Limited by legacy Qt WebKit | Low for modern CSS | Runs on your server | Medium |
| Managed Chrome API | Yes | Native browser print output | Yes | High | Sent to the API provider | Low in-app; usage fees |
Use jsPDF or html2pdf.js when the PDF is simple, client-side privacy matters, and image-based text is acceptable. Use Puppeteer or Playwright when CSS fidelity, selectable text, JavaScript execution, or reliable multipage output matters. Keep node-wkhtmltopdf only for existing legacy deployments.
Let me know which one you go with or if you discover anything I missed.