Puppeteer PDF: Complete 2025 Guide to HTML-to-PDF Generation

 PDFs and Puppeteer

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 (@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:

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

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: