html-pdf vs pdfkit vs puppeteer
Generating PDFs in Node.js Applications
html-pdfpdfkitpuppeteerSimilar Packages:

Generating PDFs in Node.js Applications

html-pdf, pdfkit, and puppeteer are three distinct approaches to generating PDF files within the JavaScript ecosystem. html-pdf is a legacy wrapper around PhantomJS that converts HTML strings to PDFs but is no longer maintained. pdfkit is a low-level PDF generation library that allows developers to draw vectors, text, and images directly using JavaScript commands without relying on a browser engine. puppeteer provides a high-level API to control headless Chrome or Chromium, enabling accurate HTML-to-PDF conversion by rendering actual web pages before saving them as documents.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
html-pdf03,574-4685 years agoMIT
pdfkit010,72110.5 MB3342 months agoMIT
puppeteer095,69943.2 kB259a day agoApache-2.0

HTML-PDF vs PDFKit vs Puppeteer: Architecture and Use Cases Compared

Generating PDFs in Node.js is a common requirement for reports, invoices, and tickets. While html-pdf, pdfkit, and puppeteer all produce PDF files, they work in fundamentally different ways. Understanding their core mechanisms helps you avoid technical debt and performance issues.

πŸ—οΈ Core Mechanism: Rendering vs Drawing

html-pdf converts HTML strings into PDFs using PhantomJS.

  • It treats the input as a web page.
  • The engine renders the HTML silently and captures the output.
  • This approach is legacy and relies on deprecated technology.
// html-pdf: Legacy HTML rendering
const pdf = require('html-pdf');
const html = '<h1>Invoice</h1><p>Total: $100</p>';

pdf.create(html).toFile('./invoice.pdf', (err, res) => {
  if (err) return console.error(err);
  console.log('File saved');
});

pdfkit draws PDF elements using vector commands.

  • You define coordinates, lines, and text positions manually.
  • There is no HTML or CSS parsing involved.
  • This gives you full control over the layout logic.
// pdfkit: Vector drawing
const PDFDocument = require('pdfkit');
const doc = new PDFDocument();
const fs = require('fs');

doc.pipe(fs.createWriteStream('./invoice.pdf'));
doc.fontSize(20).text('Invoice', 100, 100);
doc.fontSize(12).text('Total: $100', 100, 130);
doc.end();

puppeteer launches a headless Chrome browser to render content.

  • It loads HTML and CSS exactly like a user's browser.
  • It supports modern web standards including Flexbox and Grid.
  • The page is printed to PDF using the browser's native engine.
// puppeteer: Browser rendering
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setContent('<h1>Invoice</h1><p>Total: $100</p>');
  await page.pdf({ path: './invoice.pdf', format: 'A4' });
  await browser.close();
})();

πŸ“¦ Dependencies and Environment

The tools you need to install and run vary significantly between these packages.

html-pdf requires PhantomJS.

  • PhantomJS is a headless WebKit scriptable with a JavaScript API.
  • It is no longer developed or supported by its creators.
  • Installing it often fails on modern systems due to binary compatibility issues.
// html-pdf: Requires phantomjs binary
// Installation often fails on new OS versions
// npm install html-pdf
// Depends on phantomjs-prebuilt (deprecated)

pdfkit has no external binary dependencies.

  • It is pure JavaScript.
  • It runs anywhere Node.js runs without extra setup.
  • This makes deployment to serverless functions or containers very simple.
// pdfkit: Pure JS
// npm install pdfkit
// No browser or binary needed
// Works in AWS Lambda, Vercel, etc.

puppeteer downloads a Chromium browser instance.

  • It bundles a specific version of Chrome to ensure consistency.
  • The download size is large (over 100MB).
  • Serverless environments need special configuration to run the browser.
// puppeteer: Bundles Chromium
// npm install puppeteer
// Downloads Chrome binary automatically
// Requires system libraries (libgbm, etc.) on Linux

🎨 Styling and Layout Control

How you design the document changes based on the tool you pick.

html-pdf uses CSS for styling.

  • You write standard HTML and CSS classes.
  • Layout is handled by the rendering engine.
  • Support for modern CSS is limited by PhantomJS age.
// html-pdf: CSS styling
const html = `
  <style>.box { color: red; }</style>
  <div class="box">Content</div>
`;
// Limited CSS support due to old WebKit engine

pdfkit uses coordinates and drawing commands.

  • You calculate positions for every element.
  • There is no cascade or automatic flow.
  • You must write logic to handle page breaks manually.
// pdfkit: Coordinate styling
const y = 100;
doc.fillColor('red').text('Content', 50, y);
// Must manually calculate y + height for next element

puppeteer uses full modern CSS.

  • You can use external stylesheets or inline styles.
  • Layout engines like Flexbox and Grid work as expected.
  • Page breaks can be controlled via CSS print media queries.
// puppeteer: Modern CSS
const html = `
  <style>
    .box { color: red; display: flex; }
    @media print { .no-print { display: none; } }
  </style>
  <div class="box">Content</div>
`;
// Full CSS support via Chrome engine

⚠️ Maintenance and Future Proofing

Choosing a library involves considering how long it will remain viable.

html-pdf is deprecated.

  • The package has not seen significant updates in years.
  • Security vulnerabilities in PhantomJS will not be fixed.
  • Using this introduces risk to your production system.
// html-pdf: Deprecated
// Do not use for new projects
// Risk of security issues and build failures

pdfkit is actively maintained.

  • It receives updates for bug fixes and features.
  • The API is stable and well-documented.
  • It is safe for long-term use in production.
// pdfkit: Active
// Stable API
// Safe for production use

puppeteer is actively maintained by Google.

  • It tracks Chrome releases closely.
  • Security updates are frequent.
  • It is the standard for browser automation tasks.
// puppeteer: Active
// Maintained by Chrome team
// Regular security updates

🀝 Similarities: Shared Ground Between Packages

Despite their differences, these libraries share some common goals and patterns.

1. πŸ“„ Output Format is PDF

  • All three produce standard PDF files.
  • Files can be opened in any PDF reader.
  • Suitable for printing or digital distribution.
// All produce .pdf files
// html-pdf -> './file.pdf'
// pdfkit -> './file.pdf'
// puppeteer -> './file.pdf'

2. πŸ’Ύ Stream Support

  • Each package can write to a file stream.
  • This allows handling large documents without loading everything into memory.
  • Useful for sending files directly to HTTP responses.
// pdfkit: Direct stream
const stream = fs.createWriteStream('file.pdf');
doc.pipe(stream);

// puppeteer: Buffer or path
const pdfBuffer = await page.pdf({ path: 'file.pdf' });

// html-pdf: Callback or stream
pdf.create(html).toStream((err, stream) => { /*...*/ });

3. πŸ”§ Async Operations

  • Generating PDFs is asynchronous in all cases.
  • You must handle promises or callbacks.
  • Blocking the event loop should be avoided.
// All require async handling
// html-pdf: Callback
// pdfkit: Stream events
// puppeteer: Async/Await

πŸ“Š Summary: Key Differences

Featurehtml-pdfpdfkitpuppeteer
EnginePhantomJS (Deprecated)Pure JS VectorHeadless Chrome
InputHTML StringDrawing CommandsHTML + CSS
CSS SupportLimited (Old WebKit)NoneFull (Modern)
DependenciesPhantomJS BinaryNoneChromium Binary
Status❌ Deprecatedβœ… Activeβœ… Active

πŸ’‘ The Big Picture

html-pdf is a legacy tool β€” do not use it for new work. It relies on PhantomJS, which is dead technology. If you encounter it in an old codebase, plan to migrate away from it as soon as possible.

pdfkit is like a digital pen πŸ–ŠοΈ β€” perfect for drawing structured documents from data. Use it for invoices, tickets, or certificates where you control every pixel and do not need HTML layouts.

puppeteer is like a robot browser πŸ€– β€” ideal for converting web pages to PDFs. Use it when you have existing HTML templates or need complex CSS layouts that match your website exactly.

Final Thought: For most modern web applications, puppeteer offers the best balance of fidelity and maintenance. Choose pdfkit only if you need lightweight generation without browser overhead. Avoid html-pdf entirely to prevent future technical debt.

How to Choose: html-pdf vs pdfkit vs puppeteer

  • html-pdf:

    Avoid html-pdf for new projects because it depends on PhantomJS, which is deprecated and unmaintained. Only consider this if you are maintaining a legacy system that cannot be refactored immediately. For any modern development, choose a supported alternative like puppeteer to ensure security and compatibility.

  • pdfkit:

    Choose pdfkit when you need to generate structured documents like invoices, receipts, or reports from scratch without relying on HTML or CSS. It is ideal for scenarios where you need precise control over vector graphics, coordinates, and low-level PDF features without the overhead of a browser engine.

  • puppeteer:

    Choose puppeteer when you need to convert existing HTML pages or complex CSS layouts into PDFs with high fidelity. It is the best choice for rendering modern web designs, including flexbox, grid, and external stylesheets, because it uses a real Chrome instance to process the content.

README for html-pdf

node-html-pdf

HTML to PDF converter that uses phantomjs

image
Example Business Card
-> and its Source file

Example Receipt

Changelog

Have a look at the releases page: https://github.com/marcbachmann/node-html-pdf/releases

Installation

Install the html-pdf utility via npm:

$ npm install -g html-pdf

Command-line example

$ html-pdf test/businesscard.html businesscard.pdf

Code example

var fs = require('fs');
var pdf = require('html-pdf');
var html = fs.readFileSync('./test/businesscard.html', 'utf8');
var options = { format: 'Letter' };

pdf.create(html, options).toFile('./businesscard.pdf', function(err, res) {
  if (err) return console.log(err);
  console.log(res); // { filename: '/app/businesscard.pdf' }
});

API

var pdf = require('html-pdf');
pdf.create(html).toFile([filepath, ]function(err, res){
  console.log(res.filename);
});

pdf.create(html).toStream(function(err, stream){
  stream.pipe(fs.createWriteStream('./foo.pdf'));
});

pdf.create(html).toBuffer(function(err, buffer){
  console.log('This is a buffer:', Buffer.isBuffer(buffer));
});


// for backwards compatibility
// alias to pdf.create(html[, options]).toBuffer(callback)
pdf.create(html [, options], function(err, buffer){});

Footers and Headers

html-pdf can read the header or footer either out of the footer and header config object or out of the html source. You can either set a default header & footer or overwrite that by appending a page number (1 based index) to the id="pageHeader" attribute of a html tag.

You can use any combination of those tags. The library tries to find any element, that contains the pageHeader or pageFooter id prefix.

<div id="pageHeader">Default header</div>
<div id="pageHeader-first">Header on first page</div>
<div id="pageHeader-2">Header on second page</div>
<div id="pageHeader-3">Header on third page</div>
<div id="pageHeader-last">Header on last page</div>
...
<div id="pageFooter">Default footer</div>
<div id="pageFooter-first">Footer on first page</div>
<div id="pageFooter-2">Footer on second page</div>
<div id="pageFooter-last">Footer on last page</div>

Options

config = {

  // Export options
  "directory": "/tmp",       // The directory the file gets written into if not using .toFile(filename, callback). default: '/tmp'

  // Papersize Options: http://phantomjs.org/api/webpage/property/paper-size.html
  "height": "10.5in",        // allowed units: mm, cm, in, px
  "width": "8in",            // allowed units: mm, cm, in, px
  - or -
  "format": "Letter",        // allowed units: A3, A4, A5, Legal, Letter, Tabloid
  "orientation": "portrait", // portrait or landscape

  // Page options
  "border": "0",             // default is 0, units: mm, cm, in, px
  - or -
  "border": {
    "top": "2in",            // default is 0, units: mm, cm, in, px
    "right": "1in",
    "bottom": "2in",
    "left": "1.5in"
  },

  paginationOffset: 1,       // Override the initial pagination number
  "header": {
    "height": "45mm",
    "contents": '<div style="text-align: center;">Author: Marc Bachmann</div>'
  },
  "footer": {
    "height": "28mm",
    "contents": {
      first: 'Cover page',
      2: 'Second page', // Any page number is working. 1-based index
      default: '<span style="color: #444;">{{page}}</span>/<span>{{pages}}</span>', // fallback value
      last: 'Last Page'
    }
  },


  // Rendering options
  "base": "file:///home/www/your-asset-path/", // Base path that's used to load files (images, css, js) when they aren't referenced using a host

  // Zooming option, can be used to scale images if `options.type` is not pdf
  "zoomFactor": "1", // default is 1

  // File options
  "type": "pdf",           // allowed file types: png, jpeg, pdf
  "quality": "75",         // only used for types png & jpeg

  // Script options
  "phantomPath": "./node_modules/phantomjs/bin/phantomjs", // PhantomJS binary which should get downloaded automatically
  "phantomArgs": [], // array of strings used as phantomjs args e.g. ["--ignore-ssl-errors=yes"]
  "localUrlAccess": false, // Prevent local file:// access by passing '--local-url-access=false' to phantomjs
                           // For security reasons you should keep the default value if you render arbritary html/js.
  "script": '/url',        // Absolute path to a custom phantomjs script, use the file in lib/scripts as example
  "timeout": 30000,        // Timeout that will cancel phantomjs, in milliseconds

  // Time we should wait after window load
  // accepted values are 'manual', some delay in milliseconds or undefined to wait for a render event
  "renderDelay": 1000,

  // HTTP Headers that are used for requests
  "httpHeaders": {
    // e.g.
    "Authorization": "Bearer ACEFAD8C-4B4D-4042-AB30-6C735F5BAC8B"
  },

  // To run Node application as Windows service
  "childProcessOptions": {
    "detached": true
  }

  // HTTP Cookies that are used for requests
  "httpCookies": [
    // e.g.
    {
      "name": "Valid-Cookie-Name", // required
      "value": "Valid-Cookie-Value", // required
      "domain": "localhost",
      "path": "/foo", // required
      "httponly": true,
      "secure": false,
      "expires": (new Date()).getTime() + (1000 * 60 * 60) // e.g. expires in 1 hour
    }
  ]

}

The full options object gets converted to JSON and will get passed to the phantomjs script as third argument.
There are more options concerning the paperSize, header & footer options inside the phantomjs script.