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

minkpdf

v0.3.1

Published

A standalone JavaScript PDF engine with zero package dependencies

Readme

MinkPDF

npm version npm downloads GitHub stars

Documentation

MinkPDF generates PDFs from JavaScript document definitions in browsers, Node.js, and Bun. It supports text, tables, headers, footers, and embedded TrueType fonts, with no runtime package dependencies.

The engine implements layout, pagination, font parsing and subsetting, image embedding, compression, and PDF serialization. Its document definition follows a supported subset of pdfmake's API; see compatibility and limitations before migrating.

The documentation site covers the project rationale, setup, complete API reference, a pdfmake comparison, and benchmark graphs. See building and publishing the documentation to preview it locally or deploy it to GitHub Pages.

Install

npm install minkpdf

The package includes built JavaScript ES modules and maintained .d.ts declarations. Consumers do not compile MinkPDF’s implementation or install TypeScript to run it. Use ESM imports (import), with a .mjs file or a project configured with "type": "module".

Quickstart: save a PDF in Node.js or Bun

Save this as receipt.mjs. It uses built-in Helvetica and ASCII text, so no font files are needed.

import pdf from 'minkpdf';
import { writeFile } from 'node:fs/promises';

const document = pdf.createPdf({
	info: { title: 'Receipt' },
	content: [
		{ text: 'Receipt', fontSize: 18, bold: true, margin: [0, 0, 0, 12] },
		'Payment received: USD 1,234.50'
	]
});

await writeFile('receipt.pdf', await document.getBuffer());
node receipt.mjs
# Or: bun receipt.mjs

createPdf() returns an object with these asynchronous output methods:

| Method | Result | | ---------------- | -------------------------------------------------------------------------------- | | getBuffer() | Promise<Uint8Array> containing the PDF bytes | | getBlob() | Promise<Blob> with MIME type application/pdf | | getBase64() | Promise<string> containing base64 without a data URL prefix | | getDataUrl() | Promise<string> beginning with data:application/pdf;base64, | | getStream() | Promise<ReadableStream<Uint8Array>>; buffered unless streaming() is selected | | print(target?) | Promise<void>; browser PDF viewer with a print OpenAction |

The four byte/Blob methods share one cached render. Browser printing separately caches a printable PDF; ordinary output methods never include its print action.

Examples

Download a PDF in a browser

In a browser application with a bundler, add a button and import MinkPDF in your JavaScript module:

<button id="download-pdf" type="button">Download receipt</button>
import pdf from 'minkpdf';

const button = document.querySelector('#download-pdf');
button.addEventListener('click', async () => {
	button.disabled = true;
	try {
		const receipt = pdf.createPdf({ content: ['Receipt', 'Payment received: USD 1,234.50'] });
		const url = URL.createObjectURL(await receipt.getBlob());
		const link = document.createElement('a');
		link.href = url;
		link.download = 'receipt.pdf';
		document.body.append(link);
		link.click();
		link.remove();
		setTimeout(() => URL.revokeObjectURL(url), 1000);
	} finally {
		button.disabled = false;
	}
});

Without a bundler, serve the entire built dist/ module tree, preserving its relative paths, and import its index.js inside a <script type="module">. Serve the page over HTTP(S).

Print a PDF in a browser

Call print() during a button click, or pass a window opened during that click before loading report data:

import pdf from 'minkpdf';

button.addEventListener('click', async () => {
	const target = window.open('', '_blank');
	if (!target) throw new Error('Print window blocked');
	await pdf.createPdf({ content: 'Invoice INV-001' }).print(target);
});

print(target?: Window | null) opens a window synchronously when no target is supplied, then navigates to a PDF with a print OpenAction. Its promise resolves after navigation, not after printing. Automatic printing depends on the PDF viewer honoring that action. MinkPDF releases the object URL after an afterprint event or when the target closes. A blocked popup, closed target, or rendering failure rejects the promise; an automatically opened window closes on failure. This helper is browser-only and is also available on core documents.

Unicode text and font variants

For characters such as é and ₱, embed a TrueType font containing those characters. Place Inter-Regular.ttf and Inter-Bold.ttf in a local fonts/ directory, then save this as unicode-receipt.mjs:

import pdf from 'minkpdf';
import { readFile, writeFile } from 'node:fs/promises';

const [regular, bold] = await Promise.all([
	readFile('./fonts/Inter-Regular.ttf'),
	readFile('./fonts/Inter-Bold.ttf')
]);

pdf.addVirtualFileSystem({
	'Inter-Regular.ttf': regular,
	'Inter-Bold.ttf': bold
});
pdf.addFonts({
	Inter: { normal: 'Inter-Regular.ttf', bold: 'Inter-Bold.ttf' }
});

const document = pdf.createPdf({
	defaultStyle: { font: 'Inter' },
	content: [{ text: 'Receipt', bold: true }, 'José García · ₱1,234.50']
});

await writeFile('unicode-receipt.pdf', await document.getBuffer());

Run it with node unicode-receipt.mjs or bun unicode-receipt.mjs. Font paths are relative to the working directory. The repository includes Inter fixtures in tests/fixtures/fonts/; these are not included in the npm package. Preserve their SIL Open Font License when distributing them.

Virtual files accept Uint8Array bytes or base64 strings without a data URL prefix. Register each variant you use: normal, bold, italics, and bolditalics. An unregistered variant throws an error; it does not fall back to the normal face. In a browser, load your font explicitly, for example with fetch() and new Uint8Array(await response.arrayBuffer()), before registering it.

Tables with repeated headers and page numbers

This example uses built-in Helvetica and saves a report with 100 rows. headerRows: 1 repeats the table's first row when it spans pages. The footer receives the current page number and final page count.

import pdf from 'minkpdf';
import { writeFile } from 'node:fs/promises';

const document = pdf.createPdf({
	pageSize: 'A4',
	pageMargins: [40, 40, 40, 60],
	defaultStyle: { fontSize: 10 },
	footer: (page, total) => ({
		text: `Page ${page} of ${total}`,
		alignment: 'center',
		fontSize: 8,
		margin: [0, 12, 0, 0]
	}),
	content: [
		{ text: 'Sales report', fontSize: 18, bold: true, margin: [0, 0, 0, 12] },
		{
			table: {
				headerRows: 1,
				dontBreakRows: true,
				widths: ['*', 100],
				body: [
					[
						{ text: 'Item', bold: true, fillColor: '#eee' },
						{ text: 'Amount (USD)', bold: true, fillColor: '#eee' }
					],
					...Array.from({ length: 100 }, (_, index) => [
						`Item ${index + 1}`,
						{ text: '123.45', alignment: 'right' }
					])
				]
			}
		}
	]
});

await writeFile('sales-report.pdf', await document.getBuffer());

Compatibility and limitations

The root import (minkpdf) supports these document-definition features. Advanced report properties require the optional factories described below:

  • Plain and styled inline text, stacks, columns, named styles, margins and per-side margins, alignment, font variants, line height, character spacing, and decorations. Named-style margins and cell fills apply to the styled node without leaking into children.
  • Tables with fixed, automatic, proportional, and percentage widths; nested content; combined row/column spans; cell fills and borders; custom layout callbacks; repeated headers; row heights; and rows kept together. Supply empty placeholders for covered cells. Connected row spans stay together when they fit; oversized groups split with their content, fills, and borders. Spans cannot exceed the table or cross the repeated-header boundary.
  • Absolute and relative positioning in PDF points. absolutePosition: { x, y } anchors to the current page's top-left; relativePosition: { x, y } offsets the current flow cursor. Both ignore their own margins and do not advance flow. Positioned content stays on the current page and should fit within it.
  • Page sizes and orientation, page breaks, unbreakable groups, final-page-count header/footer callbacks, cancellation watermarks, document metadata, and canvas lines.
  • Embedded TrueType fonts with Unicode cmaps, composite glyph subsetting, and searchable/selectable text; built-in Helvetica for ASCII-only documents.
  • PNG/JPEG base64 data URLs. PNG supports non-interlaced 8-bit grayscale, RGB, indexed, grayscale-alpha, and RGBA images, including transparency.

Use three- or six-digit hex colors such as #eee or #334155, or case-insensitive CSS named colors such as red, gray, and lightgray. CSS color functions such as rgb(), eight-digit hex colors, transparent, and currentColor are unsupported. Dimensions, font sizes, coordinates, and margins use PDF points (72 points = 1 inch).

Per-side properties (marginLeft, marginTop, marginRight, marginBottom) support negative values and override corresponding named-style sides. An explicit node margin takes precedence over its per-side properties. Named styles resolve in array order, with node properties taking precedence.

Additional report and document capabilities are available through explicit optional factories: lists, links/navigation/TOC, graphics, QR, a declared SVG subset, sections, snaking columns, standard fonts, tiling patterns, attachments, incremental serialization, R6 encryption, PDF/A-2b/PDF/A-3b, and semantic tagging with a PDF/UA-1 profile. See the feature compatibility guide for setup, validation, and exact limits. Full complex-script shaping is unsupported. Automated PDF/UA-1 validation covers the supplied corpus; document accessibility still requires human evaluation. Embedded fonts must contain the requested characters. Line metrics and pagination can differ from pdfmake; outputs are not pixel-identical. pdfmake-specific methods outside the API documented here are not provided.

Import document types from minkpdf/interfaces:

import type { TDocumentDefinitions } from 'minkpdf/interfaces';

const definition: TDocumentDefinitions = { content: ['Hello'] };

addFonts(), assignment to pdf.fonts, addVirtualFileSystem(), and addTableLayouts() register resources shared across documents. addFonts() merges font families; assigning pdf.fonts replaces the font dictionary. MinkPDF does not fetch fonts or images automatically. The optional minkpdf/resources helpers prepare named images and font files through an explicit caller-supplied loader; Node and browser output adapters remain separate imports.

Smaller bundles with optional features

For text using built-in Helvetica, import the core:

import { createPdf } from 'minkpdf/core';

const bytes = await createPdf({ content: 'Hello' }).getBuffer();

Use createPdfEngine() from minkpdf/core with feature factories from minkpdf/tables, minkpdf/images, and minkpdf/truetype to include only the capabilities you need. The minkpdf root import preserves its historical table/image/TrueType selection. New capabilities are opt-in. reports() selects advanced report processing without enabling content features. All other content/document factories except standardFonts() also select that backend, including tables(), images(), and trueTypeFonts(). standardFonts() alone adds font faces to the basic engine. Selecting a backend does not enable unrelated capabilities: for example, reports() still needs tables() to render tables. See the optional-feature guide and engine reference for composition, registration, and errors.

Runtime requirements

The built engine uses native Web APIs, including CompressionStream with deflate, Blob, Response, TextEncoder, atob, and btoa. Stream output requires ReadableStream. PNG support additionally uses DecompressionStream; a core bundle does not include that implementation. Optional encryption requires native crypto.subtle and crypto.getRandomValues.

| Runtime | Verified use | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Bun 1.4.0 | Full engine test suite and README file-generation examples | | Bun 1.4.2 | Full suite including feature regressions, packed-package, and consumer tree-shaking tests | | Node.js 24.12.0 | README file-generation examples using the built ES module | | Chromium | Manual browser verification of PDF generation, printable-PDF navigation, and closed-target object-URL cleanup; browser printing also has lifecycle unit coverage |

These are verified versions, not minimum version guarantees. Bun is required to build this repository and run its tests and benchmark; consumers use the built ES module.

TypeScript compatibility

JavaScript consumers do not need TypeScript. TypeScript consumers use maintained .d.ts declarations rather than compiling MinkPDF's source. The supported compiler baseline is TypeScript 5.7.3, with ES2022 and DOM library definitions; the optional Node adapter also requires @types/node.

Packed-consumer checks pass on TypeScript 5.7.3, 7.0.2 (current stable at verification), and 7.1.0-dev.20261031.1. CI checks the baseline and latest stable as required jobs, and typescript@next as an advisory early warning. This reduces implementation/compiler coupling; it does not guarantee compatibility with every future compiler.

bun scripts/check-types.js --typescript=5.7.3
bun scripts/check-types.js --typescript=latest
bun scripts/check-types.js --typescript=next

Benchmarks against pdfmake

Measured on 2026-11-01, using Bun 1.4.2 on an Apple M4 Pro, Darwin 27.2.0, arm64. Each scenario uses three warmups followed by 30 measured renders. The measured build includes the auto-width and nested table header fixes. The table compares the JavaScript implementation with pdfmake using warmed median render time; PDF sizes and page counts come from the measured outputs.

| Document | MinkPDF JS median (ms) | pdfmake 0.3.13 median (ms) | PDF bytes: MinkPDF / pdfmake | Pages: MinkPDF / pdfmake | | --- | ---: | ---: | ---: | ---: | | Short document | 0.238 | 9.668 | 7,420 / 8,176 | 1 / 1 | | 100-row report | 2.291 | 26.342 | 20,094 / 28,349 | 4 / 4 | | 1,000-row report | 19.871 | 137.349 | 73,938 / 150,051 | 33 / 34 | | Nested voucher | 0.461 | 12.993 | 12,188 / 13,718 | 1 / 1 | | Invoice overlay | 0.279 | 9.219 | 7,799 / 8,536 | 1 / 1 | | ERP registration form | 0.590 | 12.612 | 12,969 / 14,717 | 1 / 1 |

Minified browser engine sizes, excluding fonts, were 35,919 bytes for MinkPDF and 832,484 bytes for pdfmake; gzip sizes were 15,254 and 296,651 bytes respectively. MinkPDF bundles a default-API wrapper around src/index.js with Bun and smoke-tests the resulting bundle; pdfmake uses its distributed minified browser build. This measures MinkPDF's historical root engine (tables, images, and TrueType), not every optional capability. Additional factories increase the selected bundle. Full results include first-render and p95 timings, sizes, page counts, and module-import time. Consumer bundle results use built dist/ entry points and are compared within their own group; the default-root consumer measures 35,934 bytes minified and 15,261 bytes gzip.

These rows come from one local, sequential two-engine run, with MinkPDF first in each scenario. This is not a performance guarantee. Background load and run order can affect timings. The engines support different feature sets, and layout and compression can differ: the 1,000-row report above has different page counts. Poppler validates representative text in both engines' PDFs; that check does not establish visual equivalence.

Run the benchmark

bun run benchmark
bun run benchmark --runs=30 --output=/tmp/minkpdf-benchmark

The benchmark installs pdfmake 0.3.13 in a temporary directory and deletes it afterward. The project manifest does not acquire a pdfmake dependency. It uses identical definitions and font files for short documents, 100-row and 1,000-row reports, nested vouchers, positioned invoice overlays, and ERP registration forms with row spans, named-style fills, per-side margins, and named colors. Print-window latency is outside the render benchmark.

Font I/O, module imports/transpilation, and definition cloning are outside timed rendering. MinkPDF's parsed-font cache is reset before each first render and reused for warmed renders, matching its export runtime. pdfmake creates a fresh font cache for each PDF, so warmed timings reflect each engine's cache lifetime. Import measurements are a local Bun comparison, not a browser network benchmark.

--output saves the measured PDFs and results.json. --font-dir=/path/to/fonts overrides the bundled fixture font directory. The benchmark needs Bun, Poppler, and registry access for the temporary baseline installation.

Development

The engine package has no runtime or development package dependencies. Building from source requires Bun 1.4.2. All implementation and build tooling is JavaScript; the build copies the maintained declarations from types/ and requires no TypeScript compiler, Node executable, npm, or network access. Node.js, npm, and registry access are needed for package/compiler compatibility checks; the full suite also uses the independent validators listed below. Build the ES modules and declarations:

bun run build

The resulting dist/ contains shared ES modules with explicit .js relative imports, matching .d.ts declarations, and no third-party runtime code. Public type exports reference declarations rather than implementation sources. Application bundlers remove optional feature modules that are not selected.

Engine tests live in tests/ and run with Bun's built-in test runner. Poppler (pdfinfo, pdftotext, pdfimages, pdftoppm) independently reads generated PDFs. Install it first:

# macOS with Homebrew
brew install poppler
# Debian / Ubuntu
sudo apt-get install poppler-utils

Before running the full bun test suite, also install Python with pikepdf, HarfBuzz (hb-shape), and veraPDF 1.30.3. Set MINKPDF_QR_DECODER_PYTHON to an interpreter with zxing-cpp and Pillow to run the independent QR decoder gate; without it those checks are skipped. See independent test tools for versions and environment setup. These tools validate tests and are not runtime package dependencies.

Font fixtures and their SIL Open Font licenses live in tests/fixtures/fonts/. No test framework or PDF parser is installed in this package.

Package and tree-shaking tests pack and install MinkPDF in temporary consumers, alongside pinned esbuild, Rollup, and TypeScript tooling. These checks require Node.js, npm, and registry access; their temporary dependencies are removed afterward. To check bundle budgets against the current feature baseline (the historical pre-extraction baseline is retained):

bun benchmarks/tree-shaking.js --compare-baseline=benchmarks/bundle-baseline.json

Contributing

Bug reports, documentation improvements, and pull requests are welcome. See the contributing guide for setup, checks, and pull request guidance. Follow the Code of Conduct when participating in the project.

License

MinkPDF is licensed under the MIT License. The test font fixtures retain their separate SIL Open Font licenses.