
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:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
h1 { color: #333; }
</style>
</head>
<body>
<h1>Hello from Puppeteer!</h1>
<p>This is a simple HTML to PDF conversion.</p>
</body>
</html>
`;
await page.setContent(htmlContent);
await page.pdf({ path: "output.pdf", format: "A4" });
await browser.close();
})();
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.
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://example.com", {
waitUntil: "networkidle2",
});
await page.pdf({
path: "webpage.pdf",
format: "A4",
});
await browser.close();
})();
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:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
// Save directly to file
await page.pdf({ path: "document.pdf", format: "A4" });
await browser.close();
})();
Return as Buffer:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
// Get PDF as buffer
const pdfBuffer = await page.pdf({ format: "A4" });
// Use the buffer (e.g., send via HTTP response)
console.log("PDF size:", pdfBuffer.length, "bytes");
await browser.close();
})();
Stream Response:
const puppeteer = require("puppeteer");
const { createWriteStream } = require("fs");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
// Stream PDF to file
const pdfStream = await page.createPDFStream({ format: "A4" });
const writeStream = createWriteStream("streamed.pdf");
pdfStream.pipe(writeStream);
await new Promise((resolve) => writeStream.on("finish", resolve));
await browser.close();
})();
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 puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
await page.pdf({
path: "custom-size.pdf",
format: "Letter", // A4, Legal, Letter, Tabloid
landscape: true, // Portrait (false) or Landscape (true)
width: "8.5in", // Custom width
height: "11in", // Custom height
scale: 0.8, // Scale of the webpage (0.1 - 2)
});
await browser.close();
})();
Margins, print CSS, and @page rules
Set margins and use print-specific CSS:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<style>
@page {
size: A4;
margin: 20mm;
}
@media print {
body { color: black; }
.no-print { display: none; }
}
body {
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Document with Print CSS</h1>
<p>This content will appear in the PDF.</p>
<p class="no-print">This won't appear in the PDF.</p>
</body>
</html>
`;
await page.setContent(htmlContent);
await page.pdf({
path: "with-margins.pdf",
format: "A4",
margin: {
top: "20mm",
right: "15mm",
bottom: "20mm",
left: "15mm",
},
printBackground: true, // Include background colors and images
});
await browser.close();
})();
Fonts, images, and assets
Handle external resources and custom fonts:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Roboto', sans-serif;
}
img {
max-width: 100%;
height: auto;
}
</style>
</head>
<body>
<h1>Document with Custom Fonts and Images</h1>
<img src="https://example.com/image.jpg" alt="Example">
<p>Text using Google Fonts</p>
</body>
</html>
`;
await page.setContent(htmlContent, {
waitUntil: ["load", "networkidle0"], // Wait for all resources
});
await page.pdf({
path: "with-assets.pdf",
format: "A4",
printBackground: true,
});
await browser.close();
})();
Headers & footers with dynamic page numbers
Create professional documents with page numbers:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
await page.pdf({
path: "with-headers-footers.pdf",
format: "A4",
displayHeaderFooter: true,
headerTemplate: `
<div style="font-size: 10px; width: 100%; text-align: center; color: #666;">
<span>Company Name - Confidential</span>
</div>
`,
footerTemplate: `
<div style="font-size: 10px; width: 100%; text-align: center; color: #666;">
<span>Page <span class="pageNumber"></span> of <span class="totalPages"></span></span>
</div>
`,
margin: {
top: "40px", // Make room for header
bottom: "40px", // Make room for footer
},
});
await browser.close();
})();
Advanced PDF Generation with Complete Options
Create professional PDFs with comprehensive configuration including page numbers:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();
// Set viewport for consistent rendering
await page.setViewport({
width: 1200,
height: 800,
});
await page.goto("https://example.com", {
waitUntil: "networkidle2",
timeout: 30000,
});
// Generate PDF with all options
await page.pdf({
path: "professional-document.pdf",
format: "A4",
landscape: false,
printBackground: true,
displayHeaderFooter: true,
headerTemplate: `
<div style="font-size: 9px; width: 100%; padding: 5px 20px; border-bottom: 1px solid #e0e0e0;">
<span style="float: left;">Document Title</span>
<span style="float: right;">Generated on ${new Date().toLocaleDateString()}</span>
</div>
`,
footerTemplate: `
<div style="font-size: 9px; width: 100%; padding: 5px 20px; border-top: 1px solid #e0e0e0;">
<span style="float: left;">ยฉ 2025 Company Name</span>
<span style="float: right;">Page <span class="pageNumber"></span> of <span class="totalPages"></span></span>
</div>
`,
margin: {
top: "60px",
right: "40px",
bottom: "60px",
left: "40px",
},
preferCSSPageSize: false,
scale: 1,
});
await browser.close();
console.log("PDF generated successfully!");
})();
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:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Fix: Wait for content to load properly
await page.goto("https://example.com", {
waitUntil: ["load", "networkidle0"], // Wait for all resources
});
// Fix: Ensure fonts are loaded
await page.evaluateHandle("document.fonts.ready");
// Fix: Add delay for dynamic content
await page.waitForTimeout(2000);
await page.pdf({
path: "complete.pdf",
format: "A4",
printBackground: true, // Ensure backgrounds render
});
await browser.close();
})();
Timeouts vs. waitForNetworkIdle
Choose the right waiting strategy:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Strategy 1: Wait for network to be idle (recommended for most sites)
await page.goto("https://example.com", {
waitUntil: "networkidle2", // Wait until 2 or fewer connections for 500ms
timeout: 60000, // Increase timeout for slow sites
});
// Strategy 2: Wait for specific selector
await page.goto("https://example.com", {
waitUntil: "domcontentloaded",
});
await page.waitForSelector(".content-loaded", { timeout: 10000 });
// Strategy 3: Fixed timeout (use sparingly)
await page.goto("https://example.com");
await page.waitForTimeout(3000);
await page.pdf({ path: "loaded.pdf", format: "A4" });
await browser.close();
})();
Loading Google Fonts and special glyphs
Handle complex fonts and international characters:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Roboto', sans-serif;
}
.japanese {
font-family: 'Noto Sans JP', sans-serif;
}
</style>
</head>
<body>
<h1>English Text with Symbols: โ
โฅ โ</h1>
<p class="japanese">ๆฅๆฌ่ชใฎใใญในใ (Japanese text)</p>
<p>Arabic: ู
ุฑุญุจุง ุจู</p>
<p>Emoji: ๐ ๐ ๐</p>
</body>
</html>
`;
await page.setContent(htmlContent, {
waitUntil: "networkidle0",
});
// Wait for fonts to load
await page.evaluateHandle("document.fonts.ready");
await page.pdf({
path: "multilingual.pdf",
format: "A4",
printBackground: true,
});
await browser.close();
})();
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 (
@pagerules) -
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:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Method 1: Inline CSS in HTML
const htmlWithInlineCSS = `
<html>
<head>
<style>
body { font-family: Arial; color: #333; }
h1 { color: #0066cc; }
</style>
</head>
<body><h1>Styled Content</h1></body>
</html>
`;
await page.setContent(htmlWithInlineCSS);
// Method 2: External CSS file
await page.goto("https://example.com");
await page.addStyleTag({ path: "./styles.css" });
// Method 3: CSS from URL
await page.addStyleTag({
url: "https://cdn.example.com/styles.css",
});
// Method 4: Inject CSS directly
await page.addStyleTag({
content: "body { background: white; margin: 20px; }",
});
await page.pdf({ path: "styled.pdf", format: "A4" });
await browser.close();
})();
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:
const puppeteer = require("puppeteer");
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Method 1: Include library in HTML
const htmlWithLibrary = `
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<canvas id="myChart"></canvas>
<script>
// Use the libraries
const ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: { labels: ['A', 'B', 'C'], datasets: [{ data: [10, 20, 30] }] }
});
</script>
</body>
</html>
`;
await page.setContent(htmlWithLibrary, { waitUntil: "networkidle0" });
// Method 2: Inject script from file
await page.addScriptTag({ path: "./my-library.js" });
// Method 3: Inject script from URL
await page.addScriptTag({
url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js",
});
// Method 4: Inject inline script
await page.addScriptTag({
content: 'console.log("Script injected");',
});
// Wait for library to execute
await page.waitForTimeout(1000);
await page.pdf({ path: "with-library.pdf", format: "A4" });
await browser.close();
})();
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: