npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rexymayderio/html-pdf-forge

v1.4.2

Published

Generate PDFs from HTML with a clean TypeScript API. Wraps html-to-pdfmake + pdfmake into a single, ergonomic surface.

Readme

html-pdf-forge

Generate PDFs from HTML with a clean TypeScript API. No headless browser, no native bindings, deterministic output.

📖 Documentation & Playground: html-pdf-forge.rexy-jms.dev

A thin wrapper over html-to-pdfmake and pdfmake that collapses the two-step pipeline into a single, ergonomic call. Adds first-class support for templates, headers/footers, page numbering, watermarks, merge/split, password protection, QR codes, and barcodes.

import { htmlToPdf } from 'html-pdf-forge';

const pdf = await htmlToPdf('<h1>Hello</h1><p>From html-pdf-forge.</p>');
await pdf.saveToFile('./hello.pdf');

Why

| | html-pdf-forge | Raw pdfmake | Puppeteer | | ------------------ | --------------- | ----------------- | ------------------------- | | Headless browser | No | No | Yes (Chromium) | | Native build deps | No | No | No | | Install footprint | ~106 MB | ~80 MB | ~300 MB | | API surface | Single function | Multi-step wiring | Browser context | | Output determinism | Yes | Yes | Browser-version dependent |

Use html-pdf-forge when you need fast, reproducible PDFs from straightforward HTML and want to avoid Chromium's footprint. Use Puppeteer/Playwright when you need pixel-perfect rendering of complex CSS (flexbox, grid, web fonts via @font-face URL, etc.).

Install

npm install html-pdf-forge

Requires Node.js 18 or newer.

Optional dependencies

QR code and barcode features require additional packages. Install them only if you need them:

# For <pdf-qr> elements
npm install qrcode

# For <pdf-barcode> elements
npm install bwip-js

Quickstart

import { htmlToPdf } from 'html-pdf-forge';

const pdf = await htmlToPdf('<h1>Q4 Report</h1>', {
  page: {
    size: 'A4',
    orientation: 'portrait',
    margins: { top: 40, right: 40, bottom: 40, left: 40 },
  },
  metadata: {
    title: 'Q4 Report',
    author: 'Jane Doe',
    keywords: ['finance', 'Q4'],
  },
  header: '<div style="text-align:right">Q4 Report</div>',
  pageNumber: { placement: 'footer', format: 'Page {current} of {total}' },
});

await pdf.saveToFile('./q4-report.pdf');

The PdfResult returned from htmlToPdf exposes:

await result.toBuffer(); // Node.js Buffer
result.toStream(); // ReadableStream (for piping)
await result.toBase64(); // base64 string
await result.toBlob(); // Blob (browser / Node 18+)
await result.saveToFile(path); // write to disk

Core API

htmlToPdf(html, options?)

The primary entry point. All options are optional.

| Option | Type | Notes | | ------------------- | -------------------------------------------------------------- | ------------------------------------ | | page.size | 'A4' | 'LETTER' | 'LEGAL' | [width, height] | etc. | Default: 'A4' | | page.orientation | 'portrait' | 'landscape' | Default: 'portrait' | | page.margins | { top, right, bottom, left } | All optional, default 40pt each | | styles | Record<tag, pdfmake style> | Merged with sensible defaults | | resetStyles | boolean | Drops defaults entirely | | fonts | Record<name, FontDefinition> | Paths or Buffers | | defaultFont | string | Defaults to bundled Roboto | | header / footer | string \| (page, total) => string | Either static or per-page | | pageNumber | { placement, format, style } | 'header' | 'footer' | 'none' | | metadata | { title, author, subject, keywords, creator, ... } | Embedded in PDF info | | watermark | string \| WatermarkOptions | Diagonal text watermark | | protect | { userPassword, ownerPassword, permissions } | Password protection | | converterOptions | Record<string, unknown> | Pass-through to html-to-pdfmake |

HtmlPdfForge

Stateful variant for batch generation with shared defaults:

import { HtmlPdfForge } from 'html-pdf-forge';

const forge = new HtmlPdfForge({
  page: { size: 'A4' },
  metadata: { author: 'Reporting Service' },
  fonts: {
    Inter: {
      normal: './fonts/Inter-Regular.ttf',
      bold: './fonts/Inter-Bold.ttf',
    },
  },
  defaultFont: 'Inter',
});

const a = await forge.generate('<h1>Doc 1</h1>');
const b = await forge.generate('<h1>Doc 2</h1>', {
  metadata: { title: 'Custom Title' },
});

Per-call overrides win on conflicts; nested objects are deep-merged.

createTemplate(html, options?)

Mustache-backed template:

import { createTemplate } from 'html-pdf-forge';

const invoice = createTemplate(`
  <h1>Invoice #{{number}}</h1>
  <p>Bill to: <strong>{{customer}}</strong></p>
  <table>
    {{#items}}
    <tr><td>{{description}}</td><td>{{amount}}</td></tr>
    {{/items}}
  </table>
`);

const pdf = await invoice.render({
  number: 'INV-0042',
  customer: 'Acme Corp',
  items: [
    { description: 'Consulting', amount: '$500' },
    { description: 'Design', amount: '$300' },
  ],
});

render() accepts a Promise<data> too — useful when data comes from an async source.

More features

Watermarks

await htmlToPdf(html, { watermark: 'CONFIDENTIAL' });

await htmlToPdf(html, {
  watermark: { text: 'DRAFT', color: 'red', opacity: 0.15, angle: -30 },
});

Page-level passwords and permissions

await htmlToPdf(html, {
  protect: {
    userPassword: 'open123',
    ownerPassword: 'admin456',
    permissions: {
      printing: 'highResolution',
      copying: false,
      modifying: false,
    },
  },
});

Encryption is delegated to PDFKit (which pdfmake uses underneath), so no extra dependency is required.

Merge

import { mergePdfs } from 'html-pdf-forge/merge';

const merged = await mergePdfs([pdf1, await pdf2.toBuffer(), './third.pdf'], {
  metadata: { title: 'Combined Bundle' },
});
await merged.saveToFile('./bundle.pdf');

Accepts Buffer, Uint8Array, file paths, or any PdfResult interchangeably.

Split

import { splitPdf } from 'html-pdf-forge/split';

const parts = await splitPdf(buffer, [
  [1, 3],
  [4, 6],
]);
await parts[0].saveToFile('./first.pdf');
await parts[1].saveToFile('./second.pdf');

Ranges are 1-indexed and inclusive. Out-of-range or reversed ranges throw PdfSplitError.

QR codes

Requires: npm install qrcode

Drop a <pdf-qr> element into your HTML — the pipeline auto-renders it to an embedded image.

<pdf-qr value="https://example.com/abc-123" size="120" margin="1" ec="M" />

| Attribute | Default | Notes | | --------- | -------- | ------------------------------------------ | | value | required | Text or URL to encode | | size | 120 | Pixel size | | margin | 1 | Quiet-zone modules | | ec | M | Error correction: L | M | Q | H |

Barcodes

Requires: npm install bwip-js

<pdf-barcode type="code128" value="ABC-12345" width="220" height="70" />

| Attribute | Default | Notes | | ------------------- | -------- | ------------------------------------------------------------------------------------------------------- | | type (or bcid) | required | Any bwip-js symbology (code128, ean13, code39, qrcode, etc.) | | value (or text) | required | Payload | | width | 200 | Output width in pixels | | height | 60 | Output height in pixels | | scale | 3 | bwip-js scale factor | | includetext | true | Render human-readable text below the barcode |

Subpath imports

Heavy features are available as separate entry points so you can import only what you need:

import { htmlToPdf } from 'html-pdf-forge'; // Core PDF generation
import { mergePdfs } from 'html-pdf-forge/merge'; // PDF merging
import { splitPdf } from 'html-pdf-forge/split'; // PDF splitting
import { inlineQrCodes } from 'html-pdf-forge/qr'; // QR preprocessing
import { inlineBarcodes } from 'html-pdf-forge/barcode'; // Barcode preprocessing

Font loading

By default, the library uses pdfmake's bundled Roboto font. To avoid loading the ~3MB VFS font data, specify a built-in web font:

const pdf = await htmlToPdf(html, { defaultFont: 'Helvetica' });

Built-in web fonts (fetched from CDN on first use, cached in memory):

  • Helvetica — uses Arimo (metrically identical to Helvetica/Arial)
  • Inter — modern sans-serif

The bundled Roboto VFS is only loaded as a fallback when no defaultFont or custom fonts are provided.

Migration from raw html-to-pdfmake + pdfmake

Before

import htmlToPdfmake from 'html-to-pdfmake';
import PdfPrinter from 'pdfmake';
import { JSDOM } from 'jsdom';

const fonts = {
  Roboto: { normal: 'Roboto-Regular.ttf', bold: 'Roboto-Medium.ttf' },
};
const printer = new PdfPrinter(fonts);
const window = new JSDOM('').window;

const content = htmlToPdfmake('<h1>Hello</h1>', { window });
const doc = printer.createPdfKitDocument({
  content,
  pageSize: 'A4',
  info: { title: 'Hello' },
});

const chunks = [];
doc.on('data', (c) => chunks.push(c));
doc.on('end', () => {
  const buffer = Buffer.concat(chunks);
  // ... write to disk ...
});
doc.end();

After

import { htmlToPdf } from 'html-pdf-forge';

const pdf = await htmlToPdf('<h1>Hello</h1>', { metadata: { title: 'Hello' } });
await pdf.saveToFile('./hello.pdf');

The wrapper preserves access to html-to-pdfmake's lower-level options via converterOptions, so anything you can do with the raw library you can still do here.

Examples

Runnable examples live in examples/:

Development

npm install
npm run typecheck    # tsc --noEmit
npm run lint         # eslint .
npm run lint:fix     # eslint . --fix
npm run format       # prettier --write .
npm run format:check # prettier --check .
npm test             # vitest run
npm run build        # tsc -p tsconfig.build.json

Releases are managed with Changesets:

npm run changeset            # record an intended bump
npm run changeset:version    # apply queued changesets, bump version, write CHANGELOG
npm run changeset:publish    # build + publish to npm

CI runs lint, format check, typecheck, tests, and build on every push and PR (Node 18 / 20 / 22).

CSS support

html-to-pdfmake supports a curated subset of CSS. Use this library when:

  • Your HTML is mostly text, headings, lists, tables, and images
  • You want deterministic output across machines and Node versions
  • You need fast cold starts (Lambda, containers)

Reach for Puppeteer or Playwright when:

  • You need flexbox, grid, or absolute positioning
  • You rely on web fonts loaded via @font-face
  • You need full CSS specificity (transforms, gradients, complex pseudo-elements)

Errors

All errors thrown by html-pdf-forge extend HtmlPdfForgeError. Specific subclasses:

| Error | Thrown when | | ------------------------------------------ | --------------------------------------------- | | HtmlConversionError | The HTML can't be parsed by html-to-pdfmake | | PdfGenerationError | The pdfmake printer fails to emit bytes | | FontLoadError | A font file can't be read or decoded | | ImageProcessingError | An image fetch or read fails | | TemplateRenderError | A Mustache template is malformed | | PdfMergeError / PdfSplitError | Invalid input for merge/split | | QrCodeRenderError / BarcodeRenderError | Custom-element rendering fails |

Project status

| Area | Status | | ------------------------ | ----------------------------------------------------------------------------------------- | | Version | 1.4.2 | | Core API and feature set | Stable | | Test suite | 41 behavioral tests, vitest | | Code quality | ESLint (flat config) + Prettier, enforced in CI | | Documentation | html-pdf-forge.rexy-jms.dev + this README + JSDoc |

License

MIT