Pure Native PDF Generation

Zero dependencies. ISO 32000-1 compliant. 16 Unicode scripts with BiDi and OpenType shaping. TypeScript-first.

MIT License
npm install pdfnative
1 563+
Tests
95%+
Coverage
0
Dependencies
16
Unicode Scripts
5
PDF Standards
GitHub Stars

Everything You Need

Production-grade PDF generation with no compromises. Every feature built from scratch.

Zero Dependencies

Built from scratch in pure TypeScript — tree-shakeable, auditable, and free from supply-chain risk. Even the crypto is built in.

16 Unicode Scripts

Thai, Arabic, Hebrew, Bengali, Tamil, CJK, Cyrillic, Greek, Devanagari, and more. Full BiDi layout (UAX #9). OpenType GSUB/GPOS shaping for Thai, Arabic, Devanagari, Bengali, and Tamil.

ISO Compliant

PDF 1.7 (ISO 32000-1), PDF/A-1b/2b/2u/3b (ISO 19005), PDF/UA tagged accessibility. Structure tree, XMP metadata, ICC profiles.

Security Built-in

AES-128/256 encryption with granular permissions. CMS/PKCS#7 digital signatures — RSA and ECDSA P-256. Zero external crypto deps.

Rich Content

12 block types: tables, images, barcodes (5 ISO formats), SVG, AcroForm fields, TOC, watermarks, hyperlinks. Pure PDF vector ops — no rasterization.

Production Ready

AsyncGenerator streaming, Web Worker off-thread generation, PDF parser & modifier. 1 563+ tests, 95%+ coverage, SLSA provenance.

Simple, Powerful API

Two builders for every use case — table-centric financial reports or free-form documents.

import { buildPDFBytes, downloadBlob } from 'pdfnative';

const pdf = buildPDFBytes({
  title: 'Monthly Report',
  infoItems: [
    { label: 'Period', value: 'January 2026' },
    { label: 'Account', value: 'Main Account' },
  ],
  balanceText: 'Balance: $1,234.56',
  countText: '42 transactions',
  headers: ['Date', 'Description', 'Category', 'Amount', 'Status'],
  rows: [
    { cells: ['01/15', 'Grocery Store', 'Food', '-$45.00', ''],
      type: 'debit', pointed: false },
    { cells: ['01/16', 'Salary', 'Income', '+$3,000.00', 'X'],
      type: 'credit', pointed: true },
  ],
  footerText: 'Generated by MyApp',
});

downloadBlob(pdf, 'report.pdf');
import { buildDocumentPDFBytes } from 'pdfnative';

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: 'image', data: chartBytes, width: 400,
      align: 'center', alt: 'Revenue chart' },
    { type: 'list', style: 'bullet',
      items: ['Revenue up 15%', 'Costs down 8%', 'Net profit +23%'] },
    { type: 'barcode', format: 'qr', data: 'https://example.com',
      width: 80, ecLevel: 'M' },
    { type: 'svg', content: svgString, width: 300 },
    { type: 'formField', fieldType: 'text', name: 'notes',
      label: 'Notes', width: 400 },
  ],
  footerText: 'Confidential',
  tagged: 'pdfa2b',     // PDF/A-2b compliance
  compress: true,        // FlateDecode compression
});
import { registerFonts, loadFontData, buildDocumentPDFBytes }
  from 'pdfnative';

// Register lazy-loaded Noto Sans font data modules
registerFonts({
  th: () => import('pdfnative/fonts/noto-thai-data.js'),
  ar: () => import('pdfnative/fonts/noto-arabic-data.js'),
  bn: () => import('pdfnative/fonts/noto-bengali-data.js'),
  ja: () => import('pdfnative/fonts/noto-jp-data.js'),
});

// Load only the scripts you need
const fonts = await Promise.all(
  ['th', 'ar', 'bn', 'ja'].map(loadFontData)
);
const fontEntries = fonts.filter(Boolean).map((fd, i) => ({
  fontData: fd!, fontRef: `/F${3 + i}`,
  lang: ['th', 'ar', 'bn', 'ja'][i],
}));

const pdf = buildDocumentPDFBytes({
  title: 'Multi-Language Document',
  blocks: [
    { type: 'heading', text: 'สวัสดี — مرحبا — こんにちは', level: 1 },
    { type: 'paragraph',
      text: 'pdfnative renders Thai, Arabic (with BiDi), Bengali, ' +
            'and Japanese — all from a single API call.' },
  ],
  fontEntries,
  tagged: true,  // PDF/A-2b with structure tree
});

Try It Live

Edit the code below, then click "Generate PDF" — it runs entirely in your browser.

How It Compares

Feature-by-feature comparison with popular PDF libraries.

Feature pdfnative pdfkit jsPDF pdfmake pdf-lib
Runtime dependencies 0 6 3 3 4
TypeScript native @types @types
Unicode scripts 16 Via fontkit Custom fonts Via pdfkit Via @pdf-lib/fontkit
BiDi (Arabic / Hebrew)
PDF/A compliance 4 levels
AES encryption 128 + 256
Digital signatures RSA + ECDSA
Barcodes / QR codes 5 formats QR
SVG rendering Plugin Paths only
Interactive forms
Streaming output
PDF parser / modifier
Browser + Node.js Via bundler
Tree-shakeable

Performance

Pure string assembly with zero allocations in hot paths. Measured with vitest bench.

100 rows (Latin)
~0.9 ms
500 rows (Latin)
~3.5 ms
1 000 rows (Latin)
~7 ms
5 000 rows (Latin)
~35 ms
100 rows (Unicode)
~1.5 ms
500 rows (Unicode)
~5 ms
1 000 rows (Unicode)
~12 ms

Benchmarks run on Node.js 22, Apple M-series / Intel i7. Results may vary. Run npx vitest bench to measure on your hardware.

Architecture

Strict unidirectional dependency flow. No circular imports. Each module is independently testable.

Born from Production Needs

The code that became pdfnative was originally built inside plika.app — a personal finance application requiring multi-language PDF bank statements across 16 scripts.

Rather than depending on heavy third-party libraries, the PDF engine was written from scratch with zero dependencies, strict ISO compliance, and native Unicode shaping. It was then extracted and open-sourced as an independent library for everyone.

Discover plika.app — where it all started →