We use cookies

We use cookies to enhance your experience and analyse site usage.

Back to Blog
Tutorial8 min read

How to Generate PDFs in Node.js (2026 Guide)

Compare every approach to PDF generation in Node.js: Puppeteer DIY, wkhtmltopdf, PDFKit, and API services. See real code for each option.

The PDF generation landscape in Node.js

Generating PDFs programmatically is one of those tasks that sounds simple but quickly becomes a rabbit hole. Here's an honest comparison of every major approach available in Node.js today.

---

Option 1: Puppeteer (DIY headless Chrome)

The most powerful option — and the most complex to maintain.

import puppeteer from 'puppeteer'

const browser = await puppeteer.launch({

args: ['--no-sandbox', '--disable-setuid-sandbox'],

})

const page = await browser.newPage()

await page.setContent('<h1>Invoice</h1><p>Total: $249</p>')

const pdf = await page.pdf({ format: 'A4' })

await browser.close()

Pros: Full control, pixel-perfect output, supports any CSS. Cons: Chromium binary (~300MB), memory leaks in long-running processes, cold start latency, complex deployment (especially serverless), needs a browser pool for production, OS-level dependencies.

---

Option 2: wkhtmltopdf

A native binary that uses WebKit to render HTML to PDF.

import { exec } from 'child_process'

exec('wkhtmltopdf input.html output.pdf', (err) => {

if (err) throw err

console.log('Done')

})

Pros: Fast, lightweight, no Node.js dependencies. Cons: Outdated WebKit engine (poor CSS Grid/Flexbox support), binary installation required, harder to use in Docker/serverless, no JavaScript execution.

---

Option 3: PDFKit (programmatic PDF generation)

Build PDFs from scratch using a drawing API — no HTML rendering.

import PDFDocument from 'pdfkit'

import fs from 'fs'

const doc = new PDFDocument()

doc.pipe(fs.createWriteStream('output.pdf'))

doc.fontSize(24).text('Invoice', { align: 'center' })

doc.fontSize(12).text('Total: $249')

doc.end()

Pros: Pure Node.js, no system dependencies, good for simple documents. Cons: Can't use HTML or CSS — you draw everything manually. Complex layouts require significant code. Can't reuse existing HTML templates.

---

Option 4: HTMLPDF.dev API (recommended)

Skip the infrastructure entirely. One HTTP request, one PDF.

const response = await fetch('https://api.htmlpdf.dev/api/pdf', {

method: 'POST',

headers: {

'Authorization': 'Bearer YOUR_API_KEY',

'Content-Type': 'application/json',

},

body: JSON.stringify({

html: '<h1>Invoice</h1><p>Total: $249</p>',

format: 'A4',

margin: { top: '1in', bottom: '1in', left: '1in', right: '1in' },

}),

})

const pdf = await response.arrayBuffer()

fs.writeFileSync('invoice.pdf', Buffer.from(pdf))

Pros: No installation, no Chromium binary, no memory management, works identically in serverless/edge/Docker, latest Chrome rendering engine, full CSS support. Cons: Requires internet access, costs money above the free tier.

---

Which should you choose?

ScenarioRecommendation
Prototype or low volumeHTMLPDF.dev free tier
High volume, self-hostedDIY Puppeteer with a browser pool
No HTML, simple documentsPDFKit
Legacy systemswkhtmltopdf
Serverless / Vercel / LambdaHTMLPDF.dev API

For most teams, the API approach wins on total cost of ownership — you avoid maintaining Chromium infrastructure and the operational complexity that comes with it.

Try HTMLPDF.dev free →

Ready to get started?

Create a free account and generate your first PDF in under 2 minutes.

Get started free