
Puppeteer is the most popular tool for generating PDFs from HTML content, making it the go-to solution for developers who need reliable PDF generation in Node.js applications. Unlike simple HTML-to-PDF converters, Puppeteer uses a headless browser that can execute JavaScript, making it essential for PDFs that require dynamic data loading, AJAX requests, or client-side rendering. Whether you're looking for a puppeteer pdf generation solution, or need to convert from html to pdf, this comprehensive guide covers everything you need to know.
This 2025 guide goes beyond basic PDF generation, we'll explore advanced layouts, scaling strategies, troubleshooting common issues, and production-ready solutions. From simple HTML strings to complex multi-page documents with dynamic content, Puppeteer provides the flexibility and control you need for enterprise-grade PDF generation.
If you need production-ready PDF generation at scale, Browserless offers hosted Chrome endpoints built specifically for this use case, eliminating the complexity of managing Chrome instances in your infrastructure. For even simpler integration, our PDF REST API provides most of Puppeteer's PDF generation capabilities without requiring you to install Puppeteer at all - you get the same powerful PDF generation features through simple HTTP requests.
Puppeteer PDF Generation Quick Start
Let's start with the essential patterns for generating PDFs with Puppeteer. These examples show the most common use cases you'll encounter.
From HTML string to PDF
The most straightforward approach is converting an HTML string directly to PDF:
import puppeteer from "puppeteer-core";
import fs from 'fs';
const API_TOKEN = "YOUR_API_TOKEN_HERE";
const BROWSER_WS_ENDPOINT = `wss://production-sfo.browserless.io?token=${API_TOKEN}`;
const htmlToPdf = async (htmlContent) => {
const browser = await puppeteer.connect({
browserWSEndpoint: BROWSER_WS_ENDPOINT,
});
const page = await browser.newPage();
// Set content and wait for any dynamic content to load
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
// Generate PDF
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
margin: {
top: '20px',
right: '20px',
bottom: '20px',
left: '20px'
}
});
await browser.close();
return pdf;
};
// Example usage
const html = "
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
h1 { color: #333; }
</style>
</head>
<body>
<h1>Hello World PDF</h1>
<p>This is a sample PDF generated with Puppeteer!</p>
</body>
</html>
";
// Generate and save PDF
const pdfBuffer = await htmlToPdf(html);
fs.writeFileSync('./html-to-pdf-output.pdf', pdfBuffer);
console.log('PDF saved as: html-to-pdf-output.pdf');
From URL to PDF
Converting existing web pages to PDF is equally straightforward. This approach is ideal for generating PDFs with Puppeteer from live websites, documentation, or any online content.
import puppeteer from "puppeteer-core";
import fs from 'fs';
const API_TOKEN = "YOUR_API_TOKEN_HERE";
const BROWSER_WS_ENDPOINT = `wss://production-sfo.browserless.io?token=${API_TOKEN}`;
const urlToPdf = async (url) => {
const browser = await puppeteer.connect({
browserWSEndpoint: BROWSER_WS_ENDPOINT,
});
const page = await browser.newPage();
// Navigate to the URL
await page.goto(url, {
waitUntil: 'networkidle0',
timeout: 30000
});
// Generate PDF
const pdf = await page.pdf({
format: 'A4',
printBackground: true
});
await browser.close();
return pdf;
};
// Generate and save PDF
const pdfBuffer = await urlToPdf('https://example.com');
fs.writeFileSync('./url-to-pdf-output.pdf', pdfBuffer);
console.log('PDF saved as: url-to-pdf-output.pdf');
Save, buffer, and stream PDFs
Generating PDFs and saving them as a file or buffer is the preferred method of PDF generation with puppeteer. However if the PDF you're trying to generate is too long, it might fail with "Page crashed!" error message. PDFs as streams is the best for efficient delivery as it doesn't try to generate the PDF entirely in memory (Which causes it to crash) and then send it back, rather it sends chunks of the PDF, assuring all the data is sent.
Save to File:
await page.pdf({ path: './output.pdf', format: 'A4', printBackground: true });
Return as Buffer:
const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true });
// Use buffer for API responses, file uploads, etc.
Stream Response:
import puppeteer from "puppeteer-core";
import { Readable } from 'stream';
import fs from 'fs';
const API_TOKEN = "YOUR_API_TOKEN_HERE";
const BROWSER_WS_ENDPOINT = `wss://production-sfo.browserless.io?token=${API_TOKEN}`;
const streamPdf = async (htmlContent) => {
const browser = await puppeteer.connect({
browserWSEndpoint: BROWSER_WS_ENDPOINT,
});
const page = await browser.newPage();
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
// Create readable stream
const pdfBuffer = await page.pdf({
format: 'A4',
printBackground: true
});
await browser.close();
// Create readable stream
const stream = new Readable();
stream.push(pdfBuffer);
stream.push(null);
return stream;
};
// Example usage
const html = `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
h1 { color: #333; }
</style>
</head>
<body>
<h1>PDF Streaming</h1>
<p>This PDF was generated as a stream for efficient delivery.</p>
</body>
</html>
`;
// Generate stream and save to file
const stream = await streamPdf(html);
const writeStream = fs.createWriteStream('./stream-pdf-output.pdf');
stream.pipe(writeStream);
writeStream.on('finish', () => console.log('PDF saved as: stream-pdf-output.pdf'));
Puppeteer HTML to PDF Formatting Options
Puppeteer provides extensive control over PDF formatting and layout. Here are the key options for customizing your PDF output.
Page size & orientation
Control page dimensions and orientation:
const pdfOptions = {
format: 'A4', // 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'Letter', 'Legal'
width: '8.5in', // Custom width
height: '11in', // Custom height
landscape: false, // true for landscape orientation
preferCSSPageSize: true // Give CSS @page size priority
};
Margins, print CSS, and @page rules
Set margins and use print-specific CSS:
const pdfOptions = {
format: 'A4',
margin: { top: '1in', right: '1in', bottom: '1in', left: '1in' },
printBackground: true
};
// HTML with print CSS
const htmlWithPrintCSS = `
<style>
@page { margin: 1in; size: A4; }
@media print {
.page-break { page-break-before: always; }
.no-break { page-break-inside: avoid; }
}
</style>
`;
Fonts, images, and assets
Handle external resources and custom fonts:
// Wait for fonts to load
await page.evaluateHandle('document.fonts.ready');
// Set content with external resources
await page.setContent(`
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<style>body { font-family: 'Roboto', sans-serif; }</style>
<img src="https://via.placeholder.com/400x200" alt="Sample Image">
`, { waitUntil: 'networkidle0' });
Headers & footers with dynamic page numbers
Create professional documents with page numbers:
const pdfOptions = {
displayHeaderFooter: true,
headerTemplate: `
<div style="font-size: 10px; text-align: center; width: 100%;">
<span class="title"></span>
<span class="date"></span>
</div>
`,
footerTemplate: `
<div style="font-size: 10px; text-align: center; width: 100%;">
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>
`
};
Advanced PDF Generation with Complete Options
Create professional PDFs with comprehensive configuration including page numbers:
import puppeteer from "puppeteer-core";
const API_TOKEN = "YOUR_API_TOKEN_HERE";
const BROWSER_WS_ENDPOINT = `wss://production-sfo.browserless.io?token=${API_TOKEN}`;
const generatePdfWithCompleteOptions = async (htmlContent) => {
const browser = await puppeteer.connect({
browserWSEndpoint: BROWSER_WS_ENDPOINT,
});
const page = await browser.newPage();
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
const completePdfOptions = {
format: 'A4',
width: '8.5in',
height: '11in',
landscape: false,
scale: 1.0,
preferCSSPageSize: true,
margin: { top: '1in', right: '1in', bottom: '1in', left: '1in' },
displayHeaderFooter: true,
headerTemplate: `
<div style="font-size: 10px; text-align: center; width: 100%;">
<span class="title"></span>
<span class="date"></span>
</div>
`,
footerTemplate: `
<div style="font-size: 10px; text-align: center; width: 100%;">
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>
`,
printBackground: true,
omitBackground: false,
pageRanges: '1-5, 8, 11-13',
tagged: true,
outline: true,
timeout: 30000,
waitForFonts: true
};
const pdf = await page.pdf(completePdfOptions);
await browser.close();
return pdf;
};
Troubleshooting Puppeteer PDF Issues
Even with the best setup, you'll encounter common issues. Here's how to resolve them.
Blank pages or missing fonts
Blank pages often indicate rendering issues:
// Set a larger viewport for better rendering
await page.setViewport({ width: 1200, height: 800 });
// Wait for fonts to load
await page.evaluateHandle('document.fonts.ready');
// Add a small delay or wait for a selector to ensure rendering is complete
await new Promise(r => setTimeout(r, 2000));
await page.waitForSelector('.mySelector');
Timeouts vs. waitForNetworkIdle
Choose the right waiting strategy:
// For static content
await page.setContent(html, { waitUntil: 'domcontentloaded' });
// For content with images/fonts
await page.setContent(html, { waitUntil: 'networkidle0' });
// For dynamic content
await page.waitForFunction(() => {
return document.querySelector('.dynamic-content') !== null;
}, { timeout: 10000 });
Loading Google Fonts and special glyphs
Handle complex fonts and international characters:
// Ensure fonts are loaded
await page.evaluateHandle('document.fonts.ready');
await page.waitForTimeout(2000); // Extra time for CJK fonts
// For RTL languages
await page.evaluate(() => {
document.dir = 'rtl';
document.body.style.direction = 'rtl';
});
Puppeteer PDFs FAQ
Lets go over a few common questions regarding PDFs generated through puppeteer
Can Puppeteer convert HTML to PDF with CSS?
Yes! Puppeteer fully supports CSS when generating PDFs from HTML. It renders HTML exactly as a browser would, including:
- CSS Grid and Flexbox layouts
- Custom fonts and typography
- Background images and gradients
- Print-specific CSS (`@page` rules)
- Media queries and responsive design
How to add a stylesheet to HTML then convert to PDF?
You can include stylesheets in several ways when generating PDFs with Puppeteer:
// Method 1: Inline styles in HTML
const html = `
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
<style>
body { font-family: 'Arial', sans-serif; }
.custom-class { color: blue; }
</style>
</head>
<body>...</body>
</html>
`;
// Method 2: Inject styles after page creation
await page.addStyleTag({
url: 'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css'
});
await page.addStyleTag({
content: '.custom-class { color: blue; }'
});
How to add a library in HTML and generate a PDF?
Puppeteer supports injecting external libraries with different methods when generating PDFs from HTMLs or URLs:
// Method 1: Include external libraries in HTML
const html = `
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.0/dist/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.1/moment.min.js"></script>
</head>
<body>
<canvas id="myChart"></canvas>
<script>
// Initialize Chart.js
const ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: { labels: ['A', 'B', 'C'], datasets: [{ data: [1, 2, 3] }] }
});
</script>
</body>
</html>
`;
// Method 2: Inject scripts after page creation using addScriptTag
await page.addScriptTag({
url: 'https://cdn.jsdelivr.net/npm/chart.js@3.7.0/dist/chart.min.js'
});
// Method 3: Inject inline JavaScript
await page.addScriptTag({
content: `
// Initialize Chart.js after libraries load
const ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: { labels: ['A', 'B', 'C'], datasets: [{ data: [1, 2, 3] }] }
});
`
});
// Wait for libraries to load
await page.waitForFunction(() => {
return window.Chart;
}, { timeout: 10000 });
Can Puppeteer generate PDF without a visible browser?
Yes! When using Browserless or running Puppeteer in headless mode, no visible browser window appears. This is perfect for server-side PDF generation.
Try Browserless for Production PDF Generation
Ready to scale your PDF generation? Get started for free today!
Additional Resources
- Official Puppeteer Documentation - Complete API reference
- Chrome DevTools Protocol - Advanced browser control
- Browserless Documentation - Puppeteer in the cloud solutions
- Browserless Best Practices - Best practices using Browserless
Conclusion
Puppeteer provides the most powerful and flexible solution for generating PDFs from HTML or URL. From simple HTML strings to complex multi-page documents, Puppeteer handles it all with excellent control over formatting and layout.
For teams needing scalable puppeteer pdf generation, Browserless provides hosted Chrome endpoints with pre-configured PDF support, eliminating the complexity of managing Chrome instances in your infrastructure while providing enterprise-grade reliability and performance.
Whether you're building a simple PDF generator or a high-volume production system, Puppeteer with Browserless gives you the tools you need to create professional-quality PDFs at any scale.
If you like this article, you can check out how our clients use Browserless for different use cases: