Quick Start
One install, one function.
npm install pdfnative, thenbuildDocumentPDFBytes({ title, blocks })returns aUint8Array— synchronously, in Node, browsers, Deno and Bun. No config, no build step, no service.
Generate your first PDF in under a minute.
Install#
npm install pdfnative
Requirements: Node.js ≥ 22 · modern browsers · Deno · Bun. Zero runtime dependencies are installed.
Node.js#
import { writeFileSync } from 'node:fs';
import { buildDocumentPDFBytes } from 'pdfnative';
const pdf = buildDocumentPDFBytes({
title: 'Hello',
blocks: [
{ type: 'heading', text: 'Hello, pdfnative', level: 1 },
{ type: 'paragraph', text: 'Pure native PDF — zero dependencies, ISO 32000-1.' },
],
});
writeFileSync('hello.pdf', pdf);
For optimal compression on Node.js, enable native zlib once at startup:
import { initNodeCompression, buildDocumentPDFBytes } from 'pdfnative';
await initNodeCompression();
const pdf = buildDocumentPDFBytes(params, { compress: true });
Browser#
<script type="module">
import { buildDocumentPDFBytes, downloadBlob } from 'https://esm.sh/pdfnative@1.7.0';
document.getElementById('go').addEventListener('click', () => {
const pdf = buildDocumentPDFBytes({
title: 'Hello',
blocks: [
{ type: 'heading', text: 'Hello from the browser', level: 1 },
],
});
downloadBlob(pdf, 'hello.pdf');
});
</script>
A stored-block compression fallback works automatically — no native zlib needed in the browser.
Tabular reports — buildPDFBytes#
For bank statements, invoices, and any single-table report:
import { buildPDFBytes } from 'pdfnative';
const pdf = buildPDFBytes({
title: 'Monthly Report',
infoItems: [
{ label: 'Period', value: 'January 2026' },
{ label: 'Account', value: 'Main' },
],
balanceText: 'Balance: $1,234.56',
countText: '42 transactions',
headers: ['Date', 'Description', 'Amount'],
rows: [
{ cells: ['01/15', 'Grocery', '-$45.00'], type: 'debit', pointed: false },
{ cells: ['01/16', 'Salary', '+$3,000.00'], type: 'credit', pointed: true },
],
footerText: 'Generated by MyApp',
});
Free-form documents — buildDocumentPDFBytes#
For mixed content (manuals, articles, multi-section reports):
const pdf = buildDocumentPDFBytes({
title: 'Project Report',
blocks: [
{ type: 'toc' },
{ type: 'heading', text: 'Executive Summary', level: 1 },
{ type: 'paragraph', text: 'This quarter saw strong growth.' },
{ type: 'list', style: 'bullet', items: ['Revenue +15%', 'Costs −8%'] },
{ type: 'image', data: chartBytes, width: 400, alt: 'Revenue chart' },
{ type: 'barcode', format: 'qr', data: 'https://example.com', width: 80 },
{ type: 'pageBreak' },
{ type: 'heading', text: 'Details', level: 1 },
{ type: 'table', headers: ['Q', 'Revenue'], rows: [
{ cells: ['Q1', '$1.2M'], type: '', pointed: false },
{ cells: ['Q2', '$1.4M'], type: '', pointed: false },
] },
],
footerText: 'Confidential',
});
13 block types are available: heading, paragraph, list, table, image, link, spacer, pageBreak, toc, barcode, svg, formField, chart.
Multi-language#
import { registerFonts, loadFontData, buildDocumentPDFBytes } from 'pdfnative';
registerFonts({
th: () => import('pdfnative/fonts/noto-thai-data.js'),
ar: () => import('pdfnative/fonts/noto-arabic-data.js'),
});
const fontEntries = await Promise.all([
loadFontData('th').then(fd => ({ fontData: fd!, fontRef: '/F3', lang: 'th' })),
loadFontData('ar').then(fd => ({ fontData: fd!, fontRef: '/F4', lang: 'ar' })),
]);
const pdf = buildDocumentPDFBytes({
blocks: [{ type: 'paragraph', text: 'สวัสดี — مرحبا' }],
fontEntries,
});
Note that lang is a property of each font entry, not of a block: pdfnative detects the script per character and routes to the matching font, so a single paragraph can mix Thai, Arabic and Latin. BiDi resolution and OpenType shaping follow from that routing. See the FAQ → Fonts and Unicode for the full list of codes.
Web Worker#
createPDF is the recommended entry point — it decides between the main thread and a worker for you:
import { createPDF } from 'pdfnative';
const pdf = await createPDF(params, {
workerUrl: new URL('./pdf-worker.js', import.meta.url), // your worker script
threshold: 500, // rows above this go to the worker (default WORKER_THRESHOLD = 500)
onProgress: (p) => console.log(`${p}%`),
});
At or below the threshold (or when Worker / workerUrl is unavailable) the PDF is generated on the main thread; above it, the worker at workerUrl is spawned, with an automatic main-thread fallback if the worker fails.
To drive a worker directly, use generatePDFInWorker(workerUrl, params, { timeout, onProgress }) — note the worker URL is the first argument, and the options are timeout (ms, default 60 000) and onProgress (there is no threshold at this level).
Streaming#
import { buildDocumentPDFStream, concatChunks } from 'pdfnative';
// buildDocumentPDFStream(params, layoutOptions?, streamOptions?)
// chunkSize lives in the 3rd argument (StreamOptions)
const chunks: Uint8Array[] = [];
for await (const chunk of buildDocumentPDFStream(
params,
{}, // layoutOptions
{ chunkSize: 65536 }, // streamOptions
)) {
chunks.push(chunk);
}
const pdf = concatChunks(chunks);
The async iterable yields Uint8Array chunks as the PDF is produced — no full-document buffering. The three-argument API (params, layoutOptions, streamOptions) keeps layout concerns (tagged, compress, watermark) and streaming concerns (chunk size) separate.
Playgrounds#
The interactive playgrounds on pdfnative.dev run entirely in the browser:
- Extreme scripts — live BiDi, Tamil, Bengali + Devanagari, Arabic harakat
- Scale — 1,000 to 100,000 pages, Web Worker + true streaming
Local testing: opening the playgrounds as
file://disables the Web Worker (browsers block cross-origin Worker imports fromfile:origins). Serve the docs directory instead:npm run docs:serve # → http://localhost:5000
Command line — pdfnative-cli#
Prefer the terminal? pdfnative-cli wraps the same library with 21 commands — including render, fill, sign, verify, ltv, encrypt, decrypt, merge, split, extract, extract-text and compare:
# Install once
npm install --global pdfnative-cli
# Render a JSON document to a PDF
pdfnative render --input report.json --output report.pdf
# Sign with an RSA key (loaded from env var, never logged)
export PDFNATIVE_SIGN_KEY="$(cat private.pem)"
export PDFNATIVE_SIGN_CERT="$(cat cert.pem)"
pdfnative sign --input report.pdf --output report.signed.pdf
# Inspect any PDF (encryption, signatures, PDF/A, metadata)
pdfnative inspect --input report.signed.pdf --format text
Stdin/stdout makes it composable in shell pipelines:
cat report.json | pdfnative render | pdfnative sign | pdfnative inspect --format text
See the dedicated CLI guide for the full command reference, security model, and CI/CD recipes.
Next steps#
- Architecture — modules, builders, generation pipeline.
- CLI — pdfnative-cli command-line interface.
- Accessibility — tagged PDF, PDF/UA, PDF/A.
- FAQ — fonts, encryption, signatures, comparisons.
- Troubleshooting — common pitfalls and fixes.
- Sample generators — 242 PDFs across 37 categories (48 generators).