TypeScript libraries for PDF document creation and manipulation, with a focus on signing and encryption scenarios.
- core - Core functionality for PDF document manipulation and processing
- doc - PDF document creation and editing, forms and signatures
- font - Font handling and management for PDF documents
- copy - Module for recreating PDF documents using specified filters
- form-json - JSON form data handling for PDF documents
- repair - Tools for repairing and fixing corrupted PDF documents
- cli - Command-line interface for PDF document manipulation and analysis (
pvpdf)
packages/tests holds private test helpers and is not published.
npm install @peculiar/pdf-doc reflect-metadatanpm 7+ also installs the peer dependencies (@peculiar/x509, pkijs, asn1js, @peculiar/asn1-schema). The packages run on Node.js ^20.19.0 || >=22.12.0 and in browsers through a bundler. Any WebCrypto implementation works: Node.js's globalThis.crypto, the browser's, or @peculiar/webcrypto 2.
v2 is ESM-only and requires Node.js ^20.19.0 || >=22.12.0 (1.x declared no engines). All @peculiar/pdf-* packages move to v2 together; don't mix them with 1.x packages. CommonJS code can still require() them on these versions; Node.js 22.0–22.11 can't (ERR_REQUIRE_ESM).
- One ESM build per package: the CommonJS
build/index.jsand the ESMbuild/index.mjsare replaced by one ES module,build/index.js, withbuild/index.d.ts. Thepvpdfbinary isbuild/index.js(wasbuild/index.cjs). Deep imports intobuild/are blocked byexports. - TypeScript compiling to CommonJS: use
"module": "node20"or"nodenext"(node16fails with TS1479). ESM and bundler setups need no changes. - Jest in CommonJS mode: switch to ESM mode or Vitest, or configure an ESM-to-CommonJS transformer and make sure
transformIgnorePatternsdoesn't exclude the@peculiar/*packages,pkijsandasn1js. - Peer dependencies: pdf takes and returns
@peculiar/x509certificates and pkijs objects, checks them withinstanceof, and uses the pkijs engine and x509 crypto provider your app sets, so they must be your app's copies.@peculiar/x509^3.1.0andpkijs^4.0.0are peers of pdf-core and pdf-doc,asn1js^4.0.0of pdf-doc, and@peculiar/asn1-schema^3.0.0of pdf-doc and pdf-font. A conflicting version now fails at install (ERESOLVE) instead of at runtime. - x509 3, pkijs 4, asn1js 4: objects created with x509 1/2, pkijs 3 or asn1js 3 fail the
instanceofchecks; see their breaking changes (x509, PKI.js, ASN1.js). If you use@peculiar/webcrypto, use v2: x509 3 next to webcrypto 1 fails ECDSA signing. - Reflect polyfill:
@peculiar/x5093 no longer loads one (x509 1 did). Importreflect-metadata(orcore-js/full/reflect, or@abraham/reflection) once, before any other import. Without it, loading pdf fails withtsyringe requires a reflect polyfill. @peculiar/utilsreplacespvtsutilsandpvutils:BufferSourcein pdf's signatures is@peculiar/utils' type (ArrayBuffer,SharedArrayBufferor anyArrayBufferView). Malformed UTF-8 and lone surrogates now decode or encode to U+FFFD instead of throwingURIError. The signing callback receivesUint8Array<ArrayBuffer>.ArrayBufferresults: methods declared to returnArrayBuffernow always do (a copy that holds exactly the result). Some returned aUint8Arraybefore (PDFStream.encode/decode, the CCITTFax/JBIG2/LZW/JPX filters,PDFCopier.save),JPXFilterreturned the whole file buffer, andImage.export()returned PNG data followed by zero padding.
pdf-core keeps the events package for PDFContent, which extends EventEmitter: Node.js resolves events to its built-in module, and bundlers for browsers use the package.
import "reflect-metadata";
import * as x509 from "@peculiar/x509";
import * as pkijs from "pkijs";
import { PDFCryptoEngine } from "@peculiar/pdf-core";
import { PDFDocument } from "@peculiar/pdf-doc";
// The engine pdf uses for signing, verification and encryption (RC4, MD5 and the PDF padding rules).
pkijs.setEngine("pdf", new PDFCryptoEngine({ name: "pdf", crypto }));
x509.cryptoProvider.set(crypto);
const doc = await PDFDocument.load(pdfBytes);
const result = await doc.verify();
for (const item of result.items) {
console.log(item.states.map((state) => `${state.code}: ${state.type}`));
}See the package READMEs for creating documents, forms, signing and encryption.
See CONTRIBUTING.md.
This project is dual-licensed under the AGPL-3.0 and a Commercial License:
The library is open-source and freely available under the terms of the Affero General Public License (AGPL-3.0). This ensures that modifications and usage in network-based applications (e.g., SaaS) must be shared under the same license.
You can use the library under AGPL-3.0 if:
- Your project is open-source and distributed under a compatible license
- Your use complies with the AGPL-3.0 requirements
For use in proprietary, closed-source, or commercial projects where the AGPL is not suitable, a commercial license is available. This allows you to use the library without the AGPL's obligations.