How to Generate PDFs from HTML in Node.js

Converting HTML to PDF is one of the most common requirements in web applications. Whether you need invoices, reports, or printable documents, there are several ways to generate PDFs from HTML in Node.js. This guide covers three approaches with working code examples.

8 min read
TutorialNode.js

#Why generate PDFs from HTML?

HTML and CSS give you precise control over document layout. Instead of learning a PDF-specific library, you can use the same skills you already have. Common use cases include:

  • Invoices and receipts with dynamic data
  • Reports and dashboards exported for stakeholders
  • Contracts and agreements that need signatures
  • Tickets and boarding passes for events or travel

Leverage your existing skills

If you already know HTML and CSS, you don't need to learn a new templating language. Design your PDF in the browser, then convert it.

The challenge is rendering HTML exactly as it appears in a browser, including web fonts, CSS layouts, and images.

#Option 1: Puppeteer

Puppeteer is a Node.js library that controls a headless Chrome browser. It renders your HTML with full browser fidelity and can export to PDF.

#Installation

npm install puppeteer

#Basic example

const puppeteer = require('puppeteer');

async function htmlToPdf(html) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setContent(html, { waitUntil: 'networkidle0' });

  const pdf = await page.pdf({
    format: 'A4',
    printBackground: true,
    margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' }
  });

  await browser.close();
  return pdf;
}

// Usage
const html = `
  <html>
    <head>
      <style>
        body { font-family: Arial, sans-serif; padding: 40px; }
        h1 { color: #333; }
      </style>
    </head>
    <body>
      <h1>Invoice #1234</h1>
      <p>Thank you for your purchase.</p>
    </body>
  </html>
`;

htmlToPdf(html).then(pdf => {
  require('fs').writeFileSync('invoice.pdf', pdf);
  console.log('PDF created');
});

#Pros and cons

Pros:

  • Perfect browser rendering fidelity
  • Supports modern CSS (flexbox, grid, custom fonts)
  • Active development and large community

Cons:

  • Downloads a 300MB+ Chromium binary
  • High memory usage (150-300MB per instance)
  • Cold starts take 2-5 seconds
  • Difficult to run in serverless environments

Memory considerations

Each Puppeteer instance can consume 150-300MB of RAM. If you're generating PDFs concurrently, memory usage can spike quickly. Consider using a browser pool or queue system for production workloads.


#Option 2: wkhtmltopdf

wkhtmltopdf uses the WebKit engine to render HTML. It is a command-line tool that you can call from Node.js.

#Installation

# macOS
brew install wkhtmltopdf

# Ubuntu/Debian
sudo apt-get install wkhtmltopdf

# Node.js wrapper
npm install wkhtmltopdf

#Basic example

const wkhtmltopdf = require('wkhtmltopdf');
const fs = require('fs');

const html = `
  <html>
    <body>
      <h1>Invoice #1234</h1>
      <p>Thank you for your purchase.</p>
    </body>
  </html>
`;

wkhtmltopdf(html, { pageSize: 'A4' })
  .pipe(fs.createWriteStream('invoice.pdf'));

#Pros and cons

Pros:

  • Faster than Puppeteer for simple documents
  • Lower memory footprint
  • Works well in Docker containers

Cons:

  • Uses an older WebKit engine (limited CSS support)
  • No flexbox or CSS grid support
  • Web fonts can be problematic
  • Project is no longer actively maintained

Legacy tool

wkhtmltopdf is no longer actively maintained. While it still works for basic use cases, consider alternatives if you need modern CSS features or long-term support.


#Option 3: PDF generation API

A PDF API handles the browser rendering on dedicated infrastructure. You send HTML and receive a PDF URL or binary.

#Why use an API?

Running headless browsers in production creates operational overhead:

  • Memory management: Chrome can consume 300MB+ per instance
  • Timeouts: Complex pages may take 10+ seconds to render
  • Scaling: You need to manage browser pools for concurrent requests
  • Dependencies: Binary dependencies complicate deployments

An API abstracts this complexity. You make an HTTP request and get a PDF back.

#Example with PDFLoom

const https = require('https');

async function htmlToPdf(html, options = {}) {
  const response = await fetch('https://api.pdfloom.com/v1/convert/html', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      content: html,
      options: {
        format: options.format || 'A4',
        printBackground: true,
        ...options
      }
    })
  });

  const data = await response.json();
  return data.url; // Signed URL valid for 30 minutes
}

// Usage
const html = `
  <html>
    <head>
      <style>
        body { font-family: 'Helvetica', sans-serif; padding: 40px; }
        .header { border-bottom: 2px solid #333; padding-bottom: 20px; }
        .amount { font-size: 24px; font-weight: bold; }
      </style>
    </head>
    <body>
      <div class="header">
        <h1>Invoice #1234</h1>
      </div>
      <p>Amount due: <span class="amount">$99.00</span></p>
    </body>
  </html>
`;

htmlToPdf(html).then(url => {
  console.log('PDF available at:', url);
});

#Pros and cons

Pros:

  • No binary dependencies
  • Works in any environment (serverless, edge, containers)
  • Scales automatically
  • Consistent rendering across all requests

Cons:

  • Requires network requests (adds latency)
  • Ongoing cost per conversion
  • Depends on third-party availability

Best for serverless

PDF APIs are the only option that works reliably in serverless environments like Vercel, Netlify, or AWS Lambda where you can't install system binaries or run long-lived browser processes.


#Comparison table

FeaturePuppeteerwkhtmltopdfPDF API
CSS supportExcellentLimitedExcellent
Memory usageHighMediumNone
Setup complexityMediumMediumLow
Serverless friendlyNoPartialYes
Maintenance burdenHighMediumNone
Cost modelInfrastructureInfrastructurePer request

#Which approach should you use?

Choose Puppeteer if:

  • You need perfect browser fidelity
  • You have dedicated servers with plenty of memory
  • You are already running Chrome for other tasks

Choose wkhtmltopdf if:

  • Your HTML is simple (no flexbox, grid, or web fonts)
  • You want a lightweight solution
  • You are comfortable with a legacy tool

Choose a PDF API if:

  • You want to avoid managing headless browsers
  • You are deploying to serverless or edge environments
  • You prefer predictable per-request pricing
  • You need to scale without infrastructure changes

#Next steps

If you want to try the API approach, you can create a free PDFLoom account and get 50 credits to test your first conversions. The API documentation covers additional options like headers, footers, page ranges, and more.

For more advanced use cases, check out converting URLs to PDF or Office documents to PDF.

Related posts

9 min read

PDF Generation in Serverless Environments

Learn how to generate PDFs in serverless environments like Vercel, AWS Lambda, and Netlify. Includes working code examples and a comparison of approaches.

TutorialServerless
8 min read

Convert Any URL to PDF with a Single API Call

Learn how to convert any public URL to a PDF or screenshot using the PDFLoom API. Includes code examples in cURL, Node.js, and a full Express.js integration.

TutorialAPI