# MinkPDF documentation
> A compact JavaScript PDF engine with zero runtime package dependencies.
Version: 0.3.1
Package: [minkpdf on npm](https://www.npmjs.com/package/minkpdf)
Source: [accntech/minkpdf](https://github.com/accntech/minkpdf)
This text is generated from the same content as the documentation site. Code examples, API tables, compatibility limits, and recorded benchmark results are included below. Relative links resolve from this file's location, including on GitHub Pages project sites.
## Documentation pages
- [Meet MinkPDF](./): A compact JavaScript PDF engine. Familiar document definitions, zero runtime package dependencies, and only the features you need.
- [Your first PDF](getting-started/): Go from a document definition to a saved PDF. Start with built-in Helvetica, then add browser downloads or Unicode fonts.
- [Installation](installation/): Use built JavaScript ES modules and maintained TypeScript declarations in your application. No runtime package dependencies to install alongside MinkPDF.
- [API reference](api/): Define a document, register resources, and request output. All dimensions use PDF points: 72 points equal one inch.
- [MinkPDF & pdfmake](comparison/): A familiar document-definition model, a smaller supported surface. Understand what carries over and what your application needs to adapt.
- [Benchmark results](benchmarks/): Explore recorded render times and browser bundle sizes. Real documents, visible methodology, and the raw data behind every chart.
## Meet MinkPDF
Page: [Meet MinkPDF](./)
A compact JavaScript PDF engine. Familiar document definitions, zero runtime package dependencies, and only the features you need.
### Why MinkPDF exists
Receipts, reports, and vouchers need reliable text, tables, and pagination. Shipping a broad PDF stack for those documents can add a substantial amount of code to an application. MinkPDF focuses on this smaller, practical document workflow.
The project’s development centers on a standalone engine with zero runtime package dependencies , a compact browser bundle, and a familiar document-definition model. It implements its own layout, font handling, and PDF serialization while supporting a useful subset of pdfmake’s API.
Optional feature modules make that focus explicit: a text-only app can import the core, while an invoice app can add tables and embedded fonts. The root import is a convenient starting point for its historical tables, images, and TrueType capabilities; advanced features require explicit selection.
### Small by design
#### Describe your document
Describe content and styles in JavaScript. Let the engine lay out and paginate the document.
#### Choose your capabilities
Compose tables, images, and TrueType fonts around a shared core. Bundle what your document needs.
#### One model across runtimes
Get PDF bytes in browsers, Node.js, and Bun through the same asynchronous output methods.
A focused API
MinkPDF supports a subset of pdfmake, with different layout and pagination behavior. Check the [compatibility table](comparison/#compatibility) before migrating an existing document.
### From content to PDF bytes
Define Content + styles
→Lay out Measure + paginate
→Serialize Fonts + streams
The engine measures text, arranges content, and paginates it. It embeds and subsets registered TrueType fonts, embeds PNG/JPEG images, compresses streams with native Web APIs, and writes the PDF structure itself.
Rendering is lazy. Calling `createPdf()` prepares a document; the byte/Blob methods share one cached render. Browser printing separately caches a printable render.
### Find your way
[Start building Getting started → Generate and save a receipt in a few lines.](getting-started/)[Set up your environment Installation → ES modules, runtime requirements, and optional imports.](installation/)[Coming from pdfmake? API comparison → Keep familiar definitions and adapt output handling.](comparison/)[Explore the measurements Benchmarks → Render timings, bundle sizes, and reproducible results.](benchmarks/)
---
## Your first PDF
Page: [Your first PDF](getting-started/)
Go from a document definition to a saved PDF. Start with built-in Helvetica, then add browser downloads or Unicode fonts.
### Save a receipt in Node.js or Bun
Install the package, save the example as `receipt.mjs`, and run it. It uses ASCII text and built-in Helvetica, so no font files are needed.
```sh
npm install minkpdf
```
```
import pdf from 'minkpdf';
import { writeFile } from 'node:fs/promises';
const receipt = pdf.createPdf({
info: { title: 'Receipt' },
content: [
{ text: 'Receipt', fontSize: 18, bold: true },
'Payment received: USD 1,234.50'
]
});
await writeFile('receipt.pdf', await receipt.getBuffer());
```
```sh
node receipt.mjs
# Or: bun receipt.mjs
```
You now have `receipt.pdf` in the current working directory. `getBuffer()` returns a `Uint8Array` that Node’s `writeFile()` accepts directly.
Use ES modules
The package ships ESM. Use a `.mjs` file, or set `"type": "module"` in your application’s `package.json`. A browser app can use its existing bundler.
### Download in a browser
Add a download button, then include the JavaScript in your bundled browser application. The object URL is released after the download is started.
```html
```
```javascript
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 built `dist/` module tree over HTTP(S) and import its `index.js`. Preserve relative module paths. See [installation without a bundler](installation/#without-bundler).
### Add Unicode with an embedded font
Helvetica supports ASCII. For names like José or currency symbols like ₱, register a TrueType font that contains the requested characters. Put `Inter-Regular.ttf` and `Inter-Bold.ttf` in a local `fonts/` directory.
```
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 receipt = pdf.createPdf({
defaultStyle: { font: 'Inter' },
content: [{ text: 'Receipt', bold: true }, 'José García · ₱1,234.50']
});
await writeFile('unicode-receipt.pdf', await receipt.getBuffer());
```
Run `node unicode-receipt.mjs` or `bun unicode-receipt.mjs`. Font paths are relative to the working directory. Supply and license your own font files; the npm package does not include the repository’s Inter fixtures.
In a browser, fetch the font yourself and pass `new Uint8Array(await response.arrayBuffer())` to `addVirtualFileSystem()`. Check `response.ok` before reading the response.
Register every variant you use
Register `normal`, `bold`, `italics`, and `bolditalics` as needed. A missing variant throws an error; it does not fall back to the normal font.
### Build a more useful document
Add [tables with repeated headers](api/#tables), [page numbers](api/#pages), or [named styles](api/#styles). For smaller bundles, choose [optional feature imports](installation/#optional-features).
---
## Installation
Page: [Installation](installation/)
Use built JavaScript ES modules and maintained TypeScript declarations in your application. No runtime package dependencies to install alongside MinkPDF.
### Install the package
```sh
npm install minkpdf
# Or choose your package manager:
pnpm add minkpdf
yarn add minkpdf
bun add minkpdf
```
Use one command for your package manager. The package includes built JavaScript ES modules and maintained `.d.ts` declarations. Use ESM imports.
```typescript
import pdf from 'minkpdf';
import type { TDocumentDefinitions } from 'minkpdf/interfaces';
const definition: TDocumentDefinitions = { content: 'Hello' };
const bytes = await pdf.createPdf(definition).getBuffer();
```
### Runtime requirements
The engine uses native `CompressionStream` with `deflate`, `Blob`, `Response`, `TextEncoder`, `atob`, and `btoa`. PNG decoding also needs `DecompressionStream`.
| Runtime | Verified in this repository |
| --- | --- |
| Bun 1.4.0 | Full-engine tests and README file-generation examples |
| Bun 1.4.2 | Full suite including feature regressions, packed-package, and consumer tree-shaking checks |
| Node.js 24.12.0 | README file-generation examples using the built ES modules |
| Chromium | Manual browser verification: printable-PDF navigation and closed-target URL cleanup; lifecycle unit tests cover printing |
The Node.js and Chromium observations were recorded on 8 October 2026. The JavaScript suite and Bun 1.4.2 measurements were checked on 1 November 2026. These observations are not minimum version guarantees or fresh browser certification. Bun is needed to build this repository, but consumers use the built ES modules.
### Include only the capabilities you need
The root import enables historical tables, images, and TrueType fonts. Advanced report, graphics, security, and output capabilities require their own factories. For ASCII text with Helvetica, use `minkpdf/core`.
```javascript
import { createPdf } from 'minkpdf/core';
const bytes = await createPdf({ content: 'Hello' }).getBuffer();
```
For a report with tables and Unicode text, compose an independent engine with two features.
```javascript
import { createPdfEngine } from 'minkpdf/core';
import { tables } from 'minkpdf/tables';
import { trueTypeFonts } from 'minkpdf/truetype';
const pdf = createPdfEngine({ features: [tables(), trueTypeFonts()] });
// Register fonts on this engine before requesting a Unicode PDF.
```
| Import | What it provides |
| --- | --- |
| `minkpdf` | Historical root: text, tables, images, and TrueType fonts |
| `minkpdf/core` | `createPdf()`, `createPdfEngine()`, engine/document/feature types |
| `minkpdf/tables` | `tables()` feature factory |
| `minkpdf/images` | `images()` feature factory |
| `minkpdf/truetype` | `trueTypeFonts()` feature factory |
| `minkpdf/reports` | reports() · links, references, advanced typography and pagination |
| `minkpdf/lists` | lists() · ordered, unordered and nested lists |
| `minkpdf/toc` | toc() · generated contents and final page numbers |
| `minkpdf/columns` | columns() · equal-width snaking columns |
| `minkpdf/graphics` | graphics() · vector shapes, strokes and clipping |
| `minkpdf/svg` | svg() · the declared SVG profile |
| `minkpdf/qr` | qr() · vector QR codes |
| `minkpdf/standard-fonts` | standardFonts() · standard PDF font metrics and encodings |
| `minkpdf/shaping` | shaping() · bounded Latin glyph shaping |
| `minkpdf/patterns` | patterns() · uncolored tiling patterns |
| `minkpdf/attachments` | attachments({vfs}) · explicit embedded files |
| `minkpdf/encryption` | encryption() · R6 AES-256 |
| `minkpdf/archival` | archival() · supported PDF/A and PDF/UA profiles |
| `minkpdf/tagged` | tagged() · semantic document structure |
| `minkpdf/streaming` | streaming() · incremental serialization |
| `minkpdf/resources` | prepareDocumentResources(), prepareFontResources() |
| `minkpdf/browser-output` | downloadPdf(), openPdf() |
| `minkpdf/node` | writePdf(), bufferedPdfStream(); Node-only adapter |
| `minkpdf/interfaces` | Document definition types; use `import type` |
The factories `tables()`, `images()`, `trueTypeFonts()`, and the other content/document factories select their report backend automatically. `reports()` enables advanced semantics without adding those content factories. `standardFonts()` supplies fonts only and does not select a report backend. Standalone resource and output helpers are imported directly, rather than placed in `features`.
Application bundlers remove unreferenced modules. Feature selection happens when creating the engine; it is not inferred from runtime document content. See the [measured consumer bundle sizes](benchmarks/#optional-features).
### Serve ES modules without a bundler
Build the repository or obtain the package’s built `dist/` directory. Copy the entire module tree to your static server, for example under `/vendor/minkpdf/`. Keep the nested folders and relative imports intact.
```html
```
Serve the page over HTTP(S). A bare import such as `'minkpdf'` needs a bundler or import map. Copying only `index.js` will lose the shared modules it imports.
### Build from source
```sh
git clone https://github.com/accntech/minkpdf.git
cd minkpdf
# Use Bun 1.4.2, matching packageManager
bun run build
```
TypeScript consumers use maintained declarations with TypeScript 5.7.3 or newer and ES2022/DOM library definitions; the Node adapter also needs `@types/node`. Packed consumers have been checked with 5.7.3, stable 7.0.2, and next 7.1.0-dev.20261031.1. CI requires the baseline and latest stable; nightly checks are advisory. JavaScript consumers need no TypeScript compiler.
Building from source requires Bun. The build copies the maintained public declarations alongside the JavaScript modules without invoking a TypeScript compiler. Compatibility tests use Node.js, npm, and isolated compiler installations. Published type exports resolve to `.d.ts` files, so consumers do not compile MinkPDF’s implementation.
The complete suite requires Poppler (`pdfinfo`, `pdftotext`, `pdfimages`), Python with `pikepdf`, HarfBuzz `hb-shape`, an isolated QR-decoder Python environment selected with `MINKPDF_QR_DECODER_PYTHON`, and veraPDF 1.30.3 selected with `MINKPDF_VERAPDF` or PATH. Package tests also need Node.js, npm, and registry access. See the [contributing guide](https://github.com/accntech/minkpdf/blob/main/CONTRIBUTING.md) for the complete setup; the Poppler commands below are only one part.
```sh
# macOS
brew install poppler
# Debian / Ubuntu
sudo apt-get install poppler-utils
bun test
```
Package and tree-shaking checks use pinned tooling in temporary consumers and require Node.js, npm, and registry access. See the [contributing guide](https://github.com/accntech/minkpdf/blob/main/CONTRIBUTING.md) for the complete development workflow.
---
## API reference
Page: [API reference](api/)
Define a document, register resources, and request output. All dimensions use PDF points: 72 points equal one inch.
### createPdf(definition)
```typescript
import pdf, { createPdf } from 'minkpdf';
import type { TDocumentDefinitions } from 'minkpdf/interfaces';
import type { PdfDocument } from 'minkpdf/core';
// Signature: createPdf(definition: TDocumentDefinitions): PdfDocument
const document = createPdf({ content: 'Hello' });
const bytes = await document.getBuffer();
```
The root default object and named exports share one engine with the historical tables, images, and TrueType capabilities. New factories are selected explicitly through core. `createPdf()` accepts one document definition and returns a `PdfDocument`. Rendering begins on the first output request; later requests reuse the same rendered PDF.
### Output methods
| Method | Return type | Use it for |
| --- | --- | --- |
| `getBuffer()` | `Promise` | Files, HTTP responses, and binary storage |
| `getBlob()` | `Promise` | Browser downloads; MIME type `application/pdf` |
| `getBase64()` | `Promise` | Base64 without a data URL prefix |
| `getDataUrl()` | `Promise` | `data:application/pdf;base64,…` |
| `getStream()` | `Promise>` | Buffered by default; optional streaming() provides incremental serialization |
| `print(target?)` | `Promise` | Browser PDF viewer with a print OpenAction; optional preopened Window |
The byte/Blob output methods take no arguments and share one cached render. `print(target?: Window | null)` is browser-only and separately caches a printable render. Rendering errors reject their promises. With `streaming()`, the first incremental `getStream()` consumes that document once; later stream or buffered-output requests reject. Requesting buffered output first retains cached output behavior. Standalone browser and Node adapters provide downloads, opening, and file writing. The default stream uses the cached buffer; optional `streaming()` serializes incrementally with consumer backpressure.
```javascript
button.addEventListener('click', async () => {
const target = window.open('', '_blank');
if (!target) throw new Error('Print window blocked');
await pdf.createPdf(definition).print(target);
});
```
Call `print()` directly from a user gesture, or pass a window opened during that gesture before fetching report data. With no target, MinkPDF opens a window synchronously before rendering. The promise resolves after navigation to the printable PDF, not after the user prints. Automatic printing depends on the PDF viewer honoring the print OpenAction. Object URLs are released on an `afterprint` event or when the target closes. A blocked popup or closed target rejects; a window created by MinkPDF closes on failure. Ordinary output methods never include a print action.
### createPdfEngine(options)
```javascript
import { createPdfEngine } from 'minkpdf/core';
import { tables } from 'minkpdf/tables';
import { images } from 'minkpdf/images';
import { trueTypeFonts } from 'minkpdf/truetype';
const pdf = createPdfEngine({
features: [tables(), images(), trueTypeFonts()]
});
```
Signature: `createPdfEngine(options?: { features?: readonly PdfFeature[], validation?: 'error' | 'warn' | 'off', onDiagnostic?: (diagnostic: Diagnostic) => void }): PdfEngine`. The default is core only. Every engine has independent font dictionaries, virtual files, custom table layouts, and parsed-font caches. Register resources on the engine that creates the document.
Features are fixed at construction; duplicate descriptors throw immediately. Core includes text, Helvetica variants, stacks, columns, pagination, headers/footers, metadata, watermarks, and canvas lines.
`createPdfEngine({ features?, validation?, onDiagnostic? })` creates an independent resource registry. Validation defaults to `'error'`; `'warn'` reports ordinary unsupported properties to `onDiagnostic(diagnostic)` and `'off'` disables those diagnostics. These modes do not bypass mandatory security checks, invalid geometry, missing fonts, or unavailable renderers. Rendering callbacks are validated when evaluated; pagination callbacks should be pure because they may run again.
### Select advanced report semantics
The historical root does not include every implemented capability. Use explicit factories for advanced documents; each content factory still needs to be selected when used. This JavaScript example needs no font files because its text is ASCII.
```javascript
import { createPdfEngine } from 'minkpdf/core';
import { reports } from 'minkpdf/reports';
import { lists } from 'minkpdf/lists';
import { qr } from 'minkpdf/qr';
const pdf = createPdfEngine({ features: [reports(), lists(), qr()] });
const document = pdf.createPdf({
content: [
{ text: 'Report', id: 'summary', outline: true },
{ ul: ['First item', 'Second item'] },
{ text: 'Back to summary', linkToDestination: 'summary' },
{ qr: 'https://example.com', fit: 100 }
]
});
const bytes = await document.getBuffer();
```
| Capability | Setup and limits |
| --- | --- |
| Typography and navigation | `reports()` enables justification, style `extends`, decoration arrays and paint options, superscript/subscript, inline backgrounds, links, IDs, outlines, page/text references, and background callbacks. |
| Pagination and sections | The report backend adds named paper sizes, automatic page height, sections, and bounded dynamic page-break reflow. `toc()` adds generated contents; `columns()` adds fixed-height, equal-width snaking columns. |
| Vector content | Select `graphics()`, `svg()`, or `qr()`. SVG excludes CSS paint servers, scripts, external resources, and advanced clipping/text transforms; QR supports Model 2 versions 1–40. |
| Fonts and patterns | `standardFonts()` adds standard font encodings; `shaping()` adds selected Latin GSUB/GPOS operations, not bidi or Arabic/Indic shaping. `patterns()` accepts uncolored PDF tiling-pattern streams with tint colors. |
| Files and encryption | `attachments({vfs})` embeds explicit payloads; `encryption()` supports R6 AES-256 only. Password normalization and viewer-enforced permissions have documented limits. |
| Conformance and tagging | `archival()` supports PDF/A-2b and PDF/A-3b with embedded fonts and compatible settings. PDF/UA-1 additionally requires `tagged()`, `subset: "PDF/UA"`, `tagged: true`, title, and language. Encryption and print actions conflict with archival output. Automated fixture validation does not replace manual accessibility review. |
See the [feature compatibility guide](https://github.com/accntech/minkpdf/blob/main/docs/feature-compatibility.md) for exact profile limits, setup requirements, and unsupported combinations. Document types describe the combined API; selecting a type alone does not enable a feature.
### Browser and Node output adapters
| Helper | Result and behavior |
| --- | --- |
| `downloadPdf(document, filename?)` | `Promise`; browser download with temporary URL cleanup; default filename `document.pdf`. |
| `openPdf(document, target?)` | `Promise`; reserve a popup in a click handler or pass a preopened window. Keeps its PDF URL until that window closes. |
| `writePdf(document, filename)` | `Promise`; Node file output after buffering. Rendering failures leave an existing destination untouched; this is not an atomic filesystem-write guarantee. |
| `bufferedPdfStream(document)` | `Promise`; Node stream over the complete cached buffer. It does not stream rendering. |
```
import pdf from 'minkpdf';
import { writePdf } from 'minkpdf/node';
await writePdf(pdf.createPdf({ content: 'Receipt' }), 'receipt.pdf');
```
```javascript
import pdf from 'minkpdf';
import { downloadPdf, openPdf } from 'minkpdf/browser-output';
const document = pdf.createPdf({ content: 'Receipt' });
// Run the chosen operation from your click handler:
// await downloadPdf(document, 'receipt.pdf');
// await openPdf(document);
```
Import `minkpdf/node` only in Node.js code. Browser adapters buffer output; `openPdf()` and `print()` need popup permission and do not wait for a user to finish viewing or printing.
### Incremental PDF serialization
```javascript
import { createPdfEngine } from 'minkpdf/core';
import { streaming } from 'minkpdf/streaming';
const document = createPdfEngine({ features: [streaming()] })
.createPdf({ content: 'Streamed report' });
const reader = (await document.getStream()).getReader();
let totalBytes = 0;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
totalBytes += value.length;
// Send each value to your destination; avoid accumulating all chunks.
}
console.log(totalBytes);
```
Choose `streaming()` before creating the document. Call `getStream()` once and consume that stream, respecting destination backpressure. Starting incremental serialization makes later buffered-output and stream requests reject; requesting a buffer first uses normal cached output instead. Cancellation stops serialization. Layout, fonts, and resources still consume memory, so streaming does not make the whole rendering process constant-memory.
### Document definition
| Property | Type / behavior |
| --- | --- |
| `content` required | `Content`: string, number, content node, or array of content |
| `pageSize` | `'A4' \| 'LETTER' \| 'LEGAL' \| { width, height }`; default A4 |
| `pageOrientation` | `'portrait' \| 'landscape'`; default portrait |
| `pageMargins` | `Margins`; default 40 points on every side |
| `defaultStyle` | `Style` applied throughout the document |
| `styles` | `Record` for named styles |
| `info` | Optional `title`, `author`, `subject`, `keywords`, `creator` strings |
| `header`, `footer` | `Content` or `(page, total, size) => Content`; page numbers are one-based |
| `watermark` | String or `{ text, color?, opacity?, bold?, italics?, fontSize?, angle? }` |
`Margins` is a number for all sides, `[horizontal, vertical]`, or `[left, top, right, bottom]`. A `Size` is a number, `'auto'`, `'*'`, or a percentage string such as `'30%'`.
### Text and styles
```javascript
const definition = {
styles: {
title: { fontSize: 20, bold: true, color: '#d94248' },
muted: { color: '#667085' }
},
content: [
{ text: 'Sales report', style: 'title', margin: [0, 0, 0, 12] },
{ text: ['Total: ', { text: 'USD 100.00', bold: true }] },
{ text: 'Prepared today', style: ['muted'], italics: true }
]
};
```
| Style fields | Accepted values |
| --- | --- |
| `font`, `fontSize` | Registered family name (default Helvetica); size in points (default 12) |
| `bold`, `italics` | Booleans selecting font variants |
| `color`, `fillColor` | Three- or six-digit hex colors and case-insensitive CSS named colors, e.g. `#eee`, `red`, or `lightgray` |
| `alignment` | `'left' \| 'center' \| 'right'` |
| `lineHeight` | Numeric line-height multiplier |
| `characterSpacing` | Additional spacing in points |
| `noWrap` | Boolean preventing text wrapping |
| `margin` | `Margins` |
| `marginLeft`, `marginTop`, `marginRight`, `marginBottom` | Per-side margins in points; negative values are supported |
| `decoration` | `'underline' \| 'lineThrough' \| 'overline'` |
| `fillOpacity` | Numeric opacity for fills |
Named styles resolve in array order; node properties override them. Margins and cell fills from named styles apply to the styled node without leaking into children. Per-side margins override the corresponding named-style sides. An explicit node `margin` takes precedence over its per-side properties.
### Positioned content
```javascript
const definition = {
pageMargins: 0,
content: [
{ text: 'INV-001', absolutePosition: { x: 300, y: 30 } },
{ text: 'Customer', absolutePosition: { x: 40, y: 90 } },
{ text: 'Offset label', relativePosition: { x: 20, y: -10 } },
'Normal flow continues at the original cursor'
]
};
```
`absolutePosition: { x, y }` anchors content to the top-left of the current page, independent of page margins and enclosing columns or cells. Text wraps within the remaining page width; explicit column widths and `noWrap` remain available for preprinted forms. `relativePosition: { x, y }` offsets the current flow cursor. Both use points, ignore their own margins, and leave the flow cursor unchanged. Positioned content stays on the current page; it is not automatically paginated and should fit inside that page.
### Stacks and columns
An array of content acts as a vertical stack. Use `stack` for an explicitly styled container or `columns` for side-by-side content, with optional `columnGap` and per-column `width`.
```javascript
const definition = {
content: [{
columns: [
{ width: '*', stack: ['Customer', { text: 'Ada Lovelace', bold: true }] },
{ width: 120, text: 'Invoice 001', alignment: 'right' }
],
columnGap: 16
}]
};
```
Use `pageBreak: 'before' | 'after'` for explicit breaks and `unbreakable: true` for grouped content. Oversized content still needs to fit the page’s available area.
### Tables
```javascript
import pdf from 'minkpdf';
const report = pdf.createPdf({
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: [{
table: {
headerRows: 1,
dontBreakRows: true,
widths: ['*', 100],
body: [
[{ text: 'Item', bold: true }, { text: 'Amount', bold: true }],
...Array.from({ length: 100 }, (_, i) => [
'Item ' + (i + 1), { text: '123.45', alignment: 'right' }
])
]
}
}]
});
```
| Table property | Behavior |
| --- | --- |
| `body` | Required array of rows; cells are strings or content nodes |
| `widths` | `Size[]` for fixed, automatic, star, or percentage widths |
| `headerRows` | Number of first rows repeated when a table spans pages |
| `keepWithHeaderRows` | Number of body rows kept with the header |
| `dontBreakRows` | Keep fitting rows together across page boundaries |
| `heights` | Number, number array, or `(row) => number` |
Cells support nested content, `fillColor`, `fillOpacity`, `border: [left, top, right, bottom]`, `colSpan`, and `rowSpan`. Supply empty placeholder cells for every covered position, including later rows. Row and column spans can be combined. Merged fills and borders cover the full cell, and its content is drawn once. Connected row spans stay together when they fit; oversized groups split with their borders and fills. Spans must stay within the table and cannot cross the repeated-header boundary.
```javascript
const definition = {
content: { table: {
widths: [80, '*', '*'],
body: [
[{ text: 'Name', rowSpan: 2 }, 'GARCÍA', 'JOSÉ'],
[{}, 'Last name', 'First name'],
[{ text: 'Address', colSpan: 3 }, {}, {}]
]
} }
};
```
A table node’s `layout` can be a named layout or a `CustomTableLayout`. Register named layouts with `addTableLayouts(dictionary)`. The historical root provides `noBorders`. Selecting `tables()` also provides `headerLineOnly` and `lightHorizontalLines`, cell `verticalAlignment` (`top`, `middle`, `bottom`), `tableAlignment`, and per-side `borderColor`. Other named layouts require registration.
```javascript
pdf.addTableLayouts({
compact: {
hLineWidth: (index, node) => index === 0 ? 0 : 0.5,
vLineWidth: () => 0,
hLineColor: () => '#ddd',
paddingTop: () => 3,
paddingBottom: () => 3
}
});
// Use layout: 'compact' alongside table: { body: ... }.
```
Custom layouts also accept `vLineColor`, `paddingLeft`, `paddingRight`, `fillColor(row, node, column)` returning a supported color or `null`, and `defaultBorder`. Other line/padding callbacks receive `(index, node)`.
### Fonts and resources
| Engine member | Signature and behavior |
| --- | --- |
| `addVirtualFileSystem(vfs)` | `(Record) => void`. Merge base64 strings (without data URL prefixes) or byte arrays. Replacing a file invalidates its parsed-font cache. |
| `addFonts(dictionary)` | `(TFontDictionary) => void`. Merge registered font families. |
| `fonts` | `TFontDictionary`. Assigning a new dictionary replaces its contents; existing references preserve their identity. |
| `addTableLayouts(dictionary)` | `(Record) => void`. Merge named table layouts. |
```javascript
pdf.addVirtualFileSystem({ 'Inter-Regular.ttf': fontBytes });
pdf.addFonts({ Inter: { normal: 'Inter-Regular.ttf' } });
// Replacement, rather than merging:
pdf.fonts = { Inter: { normal: 'Inter-Regular.ttf' } };
```
Font dictionaries map family names to `{ normal, bold?, italics?, bolditalics? }`, with filenames pointing to registered virtual files. Embedded fonts must include the requested characters. Resources are shared among documents created by the same engine. MinkPDF does not fetch files automatically.
Font registration requires `trueTypeFonts()`; custom table-layout registration requires `tables()`. Both are already enabled by the root import. See the [complete Unicode example](getting-started/#unicode).
### Prepare resources with an explicit loader
`prepareDocumentResources(document, { loader? })` resolves named images to inline PNG/JPEG data URLs. `prepareFontResources(sources, { loader? })` returns `{ fonts, vfs }`; install both on an engine with `trueTypeFonts()`. Sources may be bytes, base64 data URLs, or URL/path strings and `{ url, headers? }` addresses. External sources require a caller-supplied loader returning `Promise`; font VFS registration separately accepts raw base64 strings.
```javascript
import { prepareDocumentResources } from 'minkpdf/resources';
const definition = await prepareDocumentResources({
images: { logo: '/assets/logo.png' },
content: [{ image: 'logo', width: 80 }]
}, {
loader: async (url, options) => {
const response = await fetch(url, options);
if (!response.ok) throw new Error('Image request failed');
return new Uint8Array(await response.arrayBuffer());
}
});
// Pass definition to the root or an engine with images() selected.
```
Loaders own credentials, redirects, and access policy. The helpers perform no implicit network or filesystem access and do not install resources globally. Optional `images()` prepares named inline sources automatically; prepare external sources yourself with a loader before rendering.
### Images
The image node accepts a PNG or JPEG base64 data URL. Use `width`, `height`, or `fit: [width, height]` to size it. Add `images()` to a core engine, and `tables()` if placing images in table cells.
```javascript
import pdf from 'minkpdf';
const logo = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==';
const document = pdf.createPdf({
content: [{ image: logo, fit: [40, 40] }, 'Receipt']
});
```
PNG support includes non-interlaced 8-bit grayscale, RGB, indexed, grayscale-alpha, and RGBA images with transparency. The root accepts inline data URLs. Optional `images()` additionally supports named resources and `cover: { width, height, align?, valign? }` cropping; height-only sizing preserves aspect ratio. Prepare URL/file sources with the resource helper above. Select `svg()` for SVG nodes; it is not a PNG/JPEG image format.
### Pages, headers, and decorations
```javascript
const definition = {
pageSize: 'A4',
pageOrientation: 'portrait',
pageMargins: [40, 60, 40, 60],
info: { title: 'Draft report', author: 'Finance team' },
header: { text: 'Monthly report', margin: [40, 20, 0, 0] },
footer: (page, total, size) => ({
text: 'Page ' + page + ' of ' + total,
alignment: 'center', fontSize: 8, margin: [0, 12, 0, 0]
}),
watermark: { text: 'DRAFT', opacity: 0.15, color: '#d94248' },
content: [
'Summary',
{ canvas: [{ type: 'line', x1: 0, y1: 8, x2: 240, y2: 8,
lineWidth: 0.5, lineColor: '#ddd' }] },
{ text: 'Details', pageBreak: 'before' }
]
};
```
Header/footer callbacks receive the final total page count and `size: { width, height, orientation }`. Reserve sufficient top and bottom margins for them. The historical root canvas supports lines. The optional `graphics()` feature adds shapes, fills, strokes, dashes, and clipping.
### Errors and supported limits
```javascript
try {
const bytes = await pdf.createPdf(definition).getBuffer();
// Store or send the PDF bytes.
} catch (error) {
console.error('PDF generation failed:', error);
}
```
Missing capabilities, unregistered fonts or variants, invalid colors, and margins that leave no content area produce errors. Duplicate feature descriptors and resource registration on disabled capabilities throw synchronously; render failures reject output promises.
SVG, QR codes, lists, attachments, encryption, and PDF/A are available through optional factories with explicit limits. See the [feature compatibility guide](https://github.com/accntech/minkpdf/blob/main/docs/feature-compatibility.md) for setup and supported profiles. Full complex-script shaping is unsupported. The optional PDF/UA-1 profile has automated corpus validation; reading order and alternate-text quality require human evaluation. CSS color functions such as `rgb()`, eight-digit hex colors, `transparent`, and `currentColor` are unsupported; use `fillOpacity` or watermark opacity for transparency. See the [pdfmake comparison](comparison/#compatibility) for migration limits and [public interface definitions](https://github.com/accntech/minkpdf/blob/main/types/interfaces.d.ts) for the complete types.
---
## MinkPDF & pdfmake
Page: [MinkPDF & pdfmake](comparison/)
A familiar document-definition model, a smaller supported surface. Understand what carries over and what your application needs to adapt.
Comparison baseline
The performance benchmark and feature-compatibility test corpus both use pdfmake 0.3.13 . Migration examples below target the pdfmake 0.3 browser API, whose output methods return promises. Older callback-based integrations need to adapt to MinkPDF’s asynchronous methods.
### Keep the document definition
Supported text, named styles, tables, and header/footer definitions follow a familiar model. The same definition can be passed to each engine, but line metrics and pagination can differ.
```javascript
const definition = {
content: [
{ text: 'Invoice', bold: true, fontSize: 18 },
{ table: { widths: ['*', 100], body: [
['Description', 'Amount'], ['Consulting', 'USD 100.00']
] } }
]
};
```
### How the implementations differ
| Area | MinkPDF | pdfmake 0.3.13 |
| --- | --- | --- |
| Engine | Own layout, pagination, TrueType subsetting, and PDF serialization | Uses PDFKit, linebreak, and xmldoc as runtime package dependencies |
| Entry points | Built ESM; full root or core + optional feature factories | Client build and server entry point; separate default font assets for the browser |
| Capabilities | Select report, graphics, font, security, and output capabilities at engine construction | Broader document-definition surface |
| Document creation | `createPdf(definition)` | `createPdf(definition, options?)` in 0.3 |
| Binary output | `await getBuffer()` → `Uint8Array` | Promise-based buffer, Blob, base64, data URL, and stream methods |
| Download / open / print | `print(target?)`; standalone `downloadPdf()` and `openPdf()` adapters | Client methods `download()`, `open()`, and `print()` |
| Server file output | `writePdf(document, path)` through the Node adapter | `write(path)` on the server-side document |
| Fonts | ASCII Helvetica by default; register TrueType bytes or base64 for Unicode | Default browser setup includes Roboto virtual font assets; custom fonts supported |
pdfmake sources: [0.3.13 manifest](https://github.com/bpampuch/pdfmake/blob/0.3.13/package.json), [browser setup](https://pdfmake.github.io/docs/0.3/getting-started/client-side/), [client methods](https://pdfmake.github.io/docs/0.3/getting-started/client-side/methods/), and [server setup](https://pdfmake.github.io/docs/0.3/getting-started/server-side/).
### Adapt the output step
#### pdfmake · browser bytes
```javascript
import pdfMake from 'pdfmake/build/pdfmake.js';
import pdfFonts from 'pdfmake/build/vfs_fonts.js';
pdfMake.addVirtualFileSystem(pdfFonts);
const bytes = await pdfMake
.createPdf(definition).getBuffer();
```
#### MinkPDF · browser bytes
```javascript
import pdf from 'minkpdf';
// ASCII + built-in Helvetica
const bytes = await pdf
.createPdf(definition).getBuffer();
// Embed a TrueType font for Unicode.
```
For downloads, replace `pdfMake.createPdf(definition).download('invoice.pdf')` with `getBlob()` and an object URL. The [complete browser example](getting-started/#browser-download) includes cleanup. Browser `print(target?)` supports the same preopened-window pattern as pdfmake 0.3. Standalone adapters expose `downloadPdf()`, `openPdf()`, and `writePdf()`. Documents expose `getStream()`; select `streaming()` for incremental serialization with backpressure. See the feature compatibility guide for caching and memory limits.
### Register files explicitly
For a matching font, supply the same TrueType bytes and variants to both engines. In MinkPDF, map virtual filenames to bytes or base64, then register the font dictionary and set `defaultStyle.font`.
```javascript
pdf.addVirtualFileSystem({ 'Inter-Regular.ttf': fontBytes });
pdf.addFonts({ Inter: { normal: 'Inter-Regular.ttf' } });
const document = pdf.createPdf({
defaultStyle: { font: 'Inter' },
content: 'José García · ₱100'
});
```
`addFonts()` merges families; assigning `pdf.fonts` replaces them. Register every variant your document uses. Supply local or fetched bytes yourself; URL and filesystem font fetching are not part of MinkPDF’s registration API.
### Compatibility at a glance
| Document capability | MinkPDF status |
| --- | --- |
| Styled text, stacks, columns, named styles | Supported · optional reports adds justification and style inheritance |
| Tables, repeated headers, row/column spans, custom layouts | Supported · spans cannot cross the repeated-header boundary |
| Page breaks, headers/footers, metadata, watermarks | Supported · optional graphics adds shapes |
| TrueType fonts and searchable Unicode text | Supported · requires a font containing the characters |
| PNG / JPEG base64 data URLs | Supported · PNG limits are documented in the API |
| Absolute/relative positioning, per-side margins, named colors | Supported · positioned nodes do not advance flow |
| SVG, QR codes, lists | Optional factories · declared SVG subset |
| Attachments, encryption, PDF/A | Optional factories · R6 AES-256; PDF/A-2b and PDF/A-3b |
| Semantic tagging / PDF/UA-1 | Optional profile · human accessibility review required |
| Complex-script shaping | Outside the API |
| Pixel-identical layout with pdfmake | Not guaranteed · metrics and pagination differ |
### Before switching an application
- Inventory the document features and output helpers your app uses.
- Check the supported types, colors, image formats, and font variants.
- Match each required capability to the historical root or explicit feature factories; advanced features are not all included by the root.
- Adapt downloads, file writes, and any callback-based output handling.
- Render representative real documents. Check text, totals, page breaks, and visual output.
- Measure your own workload. Use the [recorded benchmarks](benchmarks/) as a reference, not a prediction.
---
## Benchmark results
Page: [Benchmark results](benchmarks/)
Explore recorded render times and browser bundle sizes. Real documents, visible methodology, and the raw data behind every chart.
JavaScript · 01 Nov 2026Apple M4 Pro30 renders / scenario
Read the results in context
These are local sequential measurements, not a performance guarantee. Background load and run order can affect timings. The engines support different feature sets and do not produce pixel-identical documents. The 1,000-row report spans 33 / 34 pages (MinkPDF / pdfmake).
### Render performance
The measured build includes the auto-width and nested table header fixes. Identical document definitions and Inter font files, including receipts, large tables, positioned invoice overlays, and ERP registration forms. Choose a metric to inspect the recorded results.
MinkPDF JavaScriptpdfmake 0.3.13Lower render time is better
#### Warmed median
Median render time after three warmups. Lower is better.
[SVG ↓](charts/medianMs.svg)
Linear scale from zero · msMinkPDF JavaScriptpdfmake
#### 95th percentile
The 95th percentile of 30 warmed renders. Lower is better.
[SVG ↓](charts/p95Ms.svg)
Linear scale from zero · msMinkPDF JavaScriptpdfmake
#### First render
First render, including font parsing. Lower is better.
[SVG ↓](charts/firstMs.svg)
Linear scale from zero · msMinkPDF JavaScriptpdfmake
#### PDF output size
Generated PDF bytes. Layout, compression, and page counts can differ.
[SVG ↓](charts/pdfBytes.svg)
Linear scale from zero · bytesMinkPDF JavaScriptpdfmake
### Every measurement
All times are milliseconds. Each size is in bytes; page counts are included to make layout differences visible.
| Document / engine | First | Median | p95 | PDF bytes | Pages |
| --- | --- | --- | --- | --- | --- |
| short document MinkPDF JavaScript | 9.616 | 0.238 | 1.022 | 7,420 | 1 |
| short document pdfmake 0.3.13 | 28.434 | 9.668 | 12.03 | 8,176 | 1 |
| 100-row report MinkPDF JavaScript | 14.955 | 2.291 | 3.363 | 20,094 | 4 |
| 100-row report pdfmake 0.3.13 | 36.603 | 26.342 | 28.478 | 28,349 | 4 |
| 1,000-row report MinkPDF JavaScript | 24.825 | 19.871 | 21.047 | 73,938 | 33 |
| 1,000-row report pdfmake 0.3.13 | 133.324 | 137.349 | 141.254 | 150,051 | 34 |
| nested voucher MinkPDF JavaScript | 4.997 | 0.461 | 0.633 | 12,188 | 1 |
| nested voucher pdfmake 0.3.13 | 16.55 | 12.993 | 14.804 | 13,718 | 1 |
| invoice overlay MinkPDF JavaScript | 3.634 | 0.279 | 0.474 | 7,799 | 1 |
| invoice overlay pdfmake 0.3.13 | 10.047 | 9.219 | 11.151 | 8,536 | 1 |
| ERP registration form MinkPDF JavaScript | 4.351 | 0.59 | 0.892 | 12,969 | 1 |
| ERP registration form pdfmake 0.3.13 | 14.032 | 12.612 | 14.863 | 14,717 | 1 |
### Engine bundle comparison
The report measures the historical root engine against pdfmake 0.3.13. MinkPDF includes its historical tables, images, and TrueType capabilities; other optional factories are excluded. 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. Both engine sizes exclude font assets. The separate consumer measurement below includes only the features selected by each import.
#### Minified engines
JavaScript default API bundle · Bun 1.4.2 · fonts excluded
[SVG ↓](charts/engine-bytes.svg)
Linear scale from zero · bytes
#### Gzip engines
JavaScript default API bundle · Bun 1.4.2 · fonts excluded
[SVG ↓](charts/engine-gzipBytes.svg)
Linear scale from zero · bytes
| Engine | Minified bytes | Gzip bytes | Local import (ms) |
| --- | --- | --- | --- |
| MinkPDF JavaScript | 35,919 | 15,254 | 2.06 |
| pdfmake 0.3.13 | 832,484 | 296,651 | 34.597 |
### Pay for the features you use
The separate Bun 1.4.2 measurement bundles a browser consumer of each public entry point, minified, with `content: 'Hello'`. The root selects historical tables, images, and TrueType; the core omits those implementations. Enhanced factories select the report backend and can be larger than the root. Additional optional capabilities are not represented by this six-consumer measurement. Font assets are excluded.
#### Minified consumer bundles
Optional-feature measurement · Bun 1.4.2 · fonts excluded
[SVG ↓](charts/features-raw.svg)
Linear scale from zero · bytes
#### Gzip consumer bundles
Optional-feature measurement · Bun 1.4.2 · fonts excluded
[SVG ↓](charts/features-gzip.svg)
Linear scale from zero · bytes
| Consumer | Minified bytes | Gzip bytes |
| --- | --- | --- |
| namedRoot | 35,642 | 15,142 |
| defaultRoot | 35,934 | 15,261 |
| core | 22,158 | 9,709 |
| tables | 64,576 | 23,733 |
| images | 63,618 | 23,156 |
| truetype | 61,077 | 22,214 |
### How these were measured
- Render comparison: Bun 1.4.2, Apple M4 Pro, darwin 27.2.0, arm64; 3 warmups and 30 measured renders per scenario.
- The displayed JavaScript and pdfmake results come from one sequential two-engine run, with MinkPDF first in each scenario.
- Font I/O, module import, and definition cloning are outside render timings. First-render timings include font parsing. MinkPDF reuses parsed fonts across warmed renders; pdfmake creates a new font cache for each PDF.
- Identical definitions and font files go to both engines. Poppler checks representative output text and page counts; this does not establish visual equivalence.
- Import time measures local Bun module loading, not browser network delivery.
- Optional-feature bundle results use Bun 1.4.2, browser target, and minification. Engine and consumer bundles use different entry points; compare each measurement within its own group.
[Render results · JSON ↓](data/2026-11-01-table-fixes-render-benchmarks.json)[Bundle results · JSON ↓](data/2026-11-01-table-fixes-bundle-benchmarks.json)
### Reproduce the results
Clone the repository and install Bun and Poppler. Registry access is required: the render benchmark installs pdfmake 0.3.13 in a temporary directory and removes it afterward.
```sh
bun run benchmark --runs=30 --output=/tmp/minkpdf-benchmark
# Current optional-feature bundle sizes (Bun 1.4.2)
bun run build
bun benchmarks/tree-shaking.js --output=/tmp/minkpdf-bundles.json
```
`--output` on the render benchmark saves measured PDFs and `results.json`. Use `--font-dir=/path/to/fonts` to supply other font fixtures. Inspect the [benchmark source](https://github.com/accntech/minkpdf/blob/main/benchmarks/pdfmake.js) and [document definitions](https://github.com/accntech/minkpdf/blob/main/benchmarks/documents.js).