wkhtmltopdf is a command-line tool that uses the WebKit rendering engine (the foundation of early Safari) to convert HTML and CSS into PDF. Historically, it was one of the most popular open-source solutions for server-side PDF generation.
➡️ How it works:
✅ Pros:
❌ Cons:
DocRaptor is a cloud-based API that leverages the commercial PrinceXML rendering engine to convert HTML (and CSS) into high-quality PDF or Excel documents. PrinceXML itself is known for its advanced typesetting capabilities and thorough CSS support, including complex paged media features.
➡️ How it works:
✅ Pros:
❌ Cons:
For a long time, generating PDFs that perfectly mirrored a modern browser’s rendering was difficult. Older engines like WebKit in wkhtmltopdf or specialized solutions (e.g., PrinceXML) often lagged behind the latest HTML/CSS/JS capabilities found in Chrome. That changed significantly when Google introduced Headless Chrome, allowing developers to run the browser without a visible UI.
Shortly after, Puppeteer and then Playwright emerged as powerful tools for automating Headless Chrome, enabling developers to generate PDFs and screenshots with precision.
Puppeteer is a Node.js library that provides an extensive API for automating tasks in headless (or full) Chrome/Chromium. Initially released by the Google Chrome team, it quickly became popular for web scraping, testing, and HTML to PDF conversion.
➡️ How it works:
✅ Pros:
❌ Cons:
Playwright was developed by Microsoft and shares many similarities with Puppeteer. It supports automated testing and browser manipulation for Chromium, Firefox, and WebKit, although PDF generation currently works only in Chromium. Despite that limitation, Playwright’s design and multiple language SDKs make it a powerful option for diverse teams.
➡️ How it works:
✅ Pros:
❌ Cons:
Both Puppeteer and Playwright are powerful tools for PDF generation, leveraging the same Chromium engine. The choice between the two largely depends on your specific needs, such as your preferred programming language, the complexity of your project, and whether you need to automate multiple browsers beyond PDF generation.
Here’s a comparison to help you choose the right tool for your needs:
In this quick example, we’ll show how to generate an invoice PDF by rendering an EJS template in Node.js and converting it to a PDF using Playwright.
The entire example is available on GitHub if you'd like to view or clone the full project.
1. Install Node.js.
Make sure you have Node.js installed. If not, download and install it from Node.js Website.
2. Create a New Project Directory.
Open a Terminal and run:
mkdir invoice-generator
cd invoice-generator
3. Initialize a New Node.js Project.
Create a package.json
file with the following command:
npm init -y
4. Install Required Packages.
Install ejs
for templating and playwright
for generating PDFs:
npm install ejs playwright
Here’s a suggested structure for better organization:
invoice-generator/
├── data/ // Directory for data files
│ └── invoice-data.json // JSON file for invoice data
├── templates/ // Directory for HTML templates
│ └── invoice.ejs // Template for the invoice
├── generate-invoice.js // Main script to generate PDFs
└── package.json // Project configuration file
invoice.ejs
in the templates
directory. It should define the structure of your invoice and include placeholders for dynamic data.You can find my example template on
invoice-data.json
in the data
directory.invoice.ejs
template.You can find the example invoice data on
generate-invoice.js
in your project directory.Below is the complete script:
const ejs = require('ejs');
const fs = require('fs');
const {chromium} = require('playwright');
const path = require('path');
// Load invoice data from the JSON file
const invoiceData = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'invoice-data.json'), 'utf8'));
(async () => {
try {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); // Generate unique timestamp
// Render the EJS template to HTML
const templatePath = path.join(__dirname, 'templates', 'invoice.ejs');
const html = await ejs.renderFile(templatePath, invoiceData);
// Launch a headless browser using Playwright
const browser = await chromium.launch();
const page = await browser.newPage();
// Load the rendered HTML into the browser
await page.setContent(html, {waitUntil: 'load'});
// Generate the PDF and save it with a timestamped filename
const pdfPath = `invoice-${timestamp}.pdf`;
await page.pdf({
path: pdfPath,
format: 'A4',
printBackground: true
// Additional parameters can be added here
});
await browser.close();
console.log(`PDF successfully created at: ${pdfPath}`);
} catch (error) {
console.error('An error occurred while generating the invoice:', error);
}
})();
You can also find the complete script on
1. Open a terminal and navigate to the project directory:
cd invoice-generator
2. Run the script:
node generate-invoice.js
Once the script has executed successfully, you’ll find the following file in your project directory:
invoice-<timestamp>.pdf
- the final invoice ready for sharing or printing.
It’s done! You’ve successfully created the invoice PDF. 🎉
Below is a preview of the generated invoice PDF:
Playwright (and headless browsers in general) represent the modern standard for HTML to PDF conversion, providing excellent support for contemporary web layouts and interactive elements. While some projects still rely on wkhtmltopdf or specialized engines like PrinceXML, using headless Chromium ensures your PDFs accurately reflect the latest HTML/CSS/JS capabilities.
Don’t want to manage browser instances yourself? If you’re looking for a simpler route, you could opt for an API-based solution such as PDFBolt — an approach that offloads the maintenance and scaling concerns, letting you focus on your core application logic.
No matter which method you choose, we hope this brief history and tutorial will help you generate PDFs with confidence in 2025 and beyond!