createPdf(definition)#
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<Uint8Array> | Files, HTTP responses, and binary storage |
getBlob() | Promise<Blob> | Browser downloads; MIME type application/pdf |
getBase64() | Promise<string> | Base64 without a data URL prefix |
getDataUrl() | Promise<string> | data:application/pdf;base64,… |
getStream() | Promise<ReadableStream<Uint8Array>> | Buffered by default; optional streaming() provides incremental serialization |
print(target?) | Promise<void> | 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.
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)#
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.
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 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<void>; browser download with temporary URL cleanup; default filename document.pdf. |
openPdf(document, target?) | Promise<Window>; reserve a popup in a click handler or pass a preopened window. Keeps its PDF URL until that window closes. |
writePdf(document, filename) | Promise<void>; Node file output after buffering. Rendering failures leave an existing destination untouched; this is not an atomic filesystem-write guarantee. |
bufferedPdfStream(document) | Promise<Readable>; 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');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#
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<string, Style> 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#
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#
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.
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#
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.
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.
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<string, string | Uint8Array>) => 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<string, CustomTableLayout>) => void. Merge named table layouts. |
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.
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<Uint8Array>; font VFS registration separately accepts raw base64 strings.
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.
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#
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#
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 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 for migration limits and public interface definitions for the complete types.