Home  ›  Guides  ›  CLI

pdfnative-cli — Command-Line Interface Guide

Tracks the latest published pdfnative-cli (v1.4.0, built on pdfnative 1.7.0 — pins ^1.7.0). The CLI versions independently from the library. Live package versions — and the pdfnative version each one is built on — are shown at the top of the documentation home. Full history: pdfnative-cli releases.

pdfnative-cli is the official command-line interface for the pdfnative library. It exposes 21 commands in five groups — create & edit (render, fill, annotate, metadata), page tree (merge, split, extract), security (sign, verify, ltv, doc-timestamp, encrypt, decrypt), read & extract (inspect, extract-text, compare), and automation & meta (batch, doctor, schema, completion, govern) — that together cover the full document lifecycle from JSON to a signed, timestamped, archive-grade PDF with the complete PAdES ladder (B-B → B-T → B-LT → B-LTA), plus page-tree editing, markup annotations, document comparison, and an AI-governance gate, with an agent-native automation contract for autonomous AI and CI pipelines.

Why a CLI? Many real-world workflows live outside Node.js: shell scripts, CI pipelines, Docker containers, Makefiles, batch jobs, build tools written in other languages. The CLI lets all of them call pdfnative without writing JavaScript, and is fully composable through stdin/stdout pipelines.

The CLI is a pure dispatch layer over pdfnative. No PDF logic lives in the CLI itself — every command forwards to a public pdfnative API:

CLI command pdfnative API
render buildDocumentPDFBytes() / buildDocumentPDFStream() / buildDocumentPDFStreamTrue() / buildPDFBytes() (table variant = buildPDFBytes, the table-centric builder; document variant = buildDocumentPDFBytes, the free-form block builder)
fill readFormFields() / fillForm() / flattenForm()
encrypt / decrypt openPdf(bytes, { password }) + MergeOptions.encrypt
extract-text extractText()
doctor — (environment probe, no library equivalent)
sign signPdfBytes() / addSignaturePlaceholder() — the native constant-time provider is implemented in the CLI (createNativeCryptoProvider is a CLI utility, not a pdfnative API) and passed per call as options.provider (pdfnative also exposes a global setCryptoProvider(), which the CLI does not use)
inspect openPdf() / getInfo() / pageCount / getPageLabels() / getAnnotations() / validatePdfUA()
verify openPdf() + verifyCertSignature() (X.509 certificate-signature checks); the CMS/PKCS#7, RFC 3161 timestamp and OCSP/CRL revocation verification is implemented in the CLI itself
merge mergePdfs()
split splitPdf()
extract extractPages()
annotate PdfModifier.addAnnotation() / buildAnnotationBody()
metadata PdfModifier.updateMetadata() — incremental /Info + XMP edits that keep existing signatures valid for their revision
ltv collectValidationInfo() / embedValidationInfo() / addValidationInfo() (PAdES B-LT /DSS)
doc-timestamp addDocumentTimestamp() — an RFC 3161 /DocTimeStamp revision (PAdES B-LTA)
compare openPdf() / extractText() / readFormFields() / getAnnotations() / listSignatures() — text + structure diff, implemented in the CLI
govern AI-governance contract (.github/ai-governance.json, AGENT_RULES.md) — draft gating uses the CLI's own validateGovernanceDraft() (the core repo's equivalent check is a repo script, not a published API)
batch the render pipeline, applied in parallel across a directory
schema versioned JSON Schemas (Draft 2020-12) for every input/output shape

This means every feature of the library is one release away from the CLI, and any bug fix in pdfnative is automatically picked up by pdfnative-cli on its next dependency bump.


Installation#

# Run directly with npx — no global install required
npx pdfnative-cli render --input document.json --output report.pdf

# Or install globally
npm install --global pdfnative-cli
pdfnative render --input document.json --output report.pdf

Requirements: Node.js ≥ 22 (v1.4.0 — Node 20 reached end-of-life on 2026-04-30) · Bun · Deno (node dist/cli.cjs).

The CLI ships with NPM provenance — verify the published artifact with npm audit signatures or on npmjs.com.


When to use the CLI vs the library#

Use the CLI when… Use the library when…
You write shell scripts, Makefiles, or Bash/PowerShell pipelines You build a Node.js / Bun / Deno service
Your CI/CD job runs in Docker or GitHub Actions You need fine-grained streaming control or Web Worker offloading
You want to compose with cat, jq, tee, gzip, etc. You target browsers, Web Workers, or Deno Deploy
You sign, verify, or inspect PDFs ad-hoc from the terminal You bundle PDFs through a custom pipeline (custom font registry, hooks, etc.)
You want a one-liner instead of a 30-line Node.js script You need 100 % programmatic control of the API surface

The two are complementary. A typical full-stack project uses the library at runtime and the CLI in CI scripts and operator workflows.


Quick start#

1. Render a document#

Create report.json:

{
  "title": "April 2026 Report",
  "blocks": [
    { "type": "heading", "text": "April 2026 Report", "level": 1 },
    { "type": "paragraph", "text": "Summary for the financial period ending 30 April 2026." },
    { "type": "list", "style": "bullet", "items": [
      "Revenue: +18% year-on-year",
      "Net Promoter Score: 72",
      "Active customers: 12,400"
    ]},
    { "type": "table",
      "headers": ["Quarter", "Revenue", "Profit"],
      "rows": [
        { "cells": ["Q1", "$1.2M", "$400K"], "type": "credit", "pointed": false },
        { "cells": ["Q2", "$1.5M", "$600K"], "type": "credit", "pointed": true }
      ]
    }
  ],
  "footerText": "Confidential",
  "metadata": { "author": "Finance Team", "subject": "April 2026 Report" }
}

Render it:

pdfnative render --input report.json --output report.pdf

That's it — the file report.pdf is now a valid ISO 32000-1 PDF, ready to send.

2. Sign the rendered PDF (with metadata)#

# Set keys via environment variables (recommended for CI/CD — 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 \
  --reason "Approved by Finance" \
  --name "Finance Team" \
  --location "Paris, FR" \
  --signing-time 2026-04-28T10:00:00Z

The CLI accepts both RSA PKCS#1 v1.5 and ECDSA P-256 keys, both with SHA-256 digests. The signed PDF carries a CMS/PKCS#7 signature embedded as ISO 32000-1 §12.8 prescribes, validatable by Adobe Acrobat, MuPDF, and any other PAdES-compatible reader.

3. Verify embedded signatures#

pdfnative verify --input report.signed.pdf --strict --trust ca-root.pem

v0.3.0 performs real CMS/PKCS#7 verification — the CLI recomputes the byte-range digest, validates the signature value (RSA-SHA256 or ECDSA-SHA256), walks the certificate chain via pdfnative's verifyCertSignature, evaluates trust against --trust roots and self-signed acceptance, and reports the presence of an RFC 3161 timestamp token. Exit code is 0 on success, 1 on any failure under --strict.

A sample JSON report:

{
  "signatures": [
    {
      "integrity": true,
      "signatureValid": true,
      "signatureAlgorithm": "ecdsa-sha256",
      "chainValid": true,
      "trustedRoot": true,
      "timestampPresent": false,
      "signerSubject": "CN=pdfnative-cli ECDSA Test, O=pdfnative-cli, C=FR",
      "signerIssuer": "CN=pdfnative-cli ECDSA Test, O=pdfnative-cli, C=FR",
      "notes": ["no --trust provided; accepted self-signed root"]
    }
  ]
}

4. Inspect any PDF#

pdfnative inspect --input report.signed.pdf --format text
Version:        1.7
Pages:          2
Encrypted:      no
PDF/A:          none
Signatures:     1
Title:          April 2026 Report
Author:         Finance Team
Created:        2026-04-28T10:00:00+00:00
Subject:        April 2026 Report
Producer:       —

JSON output (default) is suited for piping into jq or storing as a CI artifact.


Hybrid layout model#

render adopts the same layout philosophy as gh, kubectl, and docker: high-frequency knobs are first-class flags, while the full PdfLayoutOptions shape is reachable via a JSON layout file.

Precedence: CLI flags > --layout file > pdfnative defaults.

# 1) Flags only — best for ad-hoc invocations
pdfnative render --input doc.json --output report.pdf \
  --page-size A4 --margin 50 --compress --tagged pdfa2b

# 2) Layout file only — best for reproducible CI configs
pdfnative render --input doc.json --output report.pdf \
  --layout layout.json

# 3) Hybrid — base config in a file, per-job overrides on the CLI
pdfnative render --input doc.json --output report.pdf \
  --layout layout.json \
  --watermark-text "DRAFT $(date +%Y-%m-%d)"

layout.json accepts any subset of PdfLayoutOptions:

{
  "pageSize": { "width": 595, "height": 842 },
  "margin": { "top": 60, "right": 50, "bottom": 60, "left": 50 },
  "compress": true,
  "tagged": "pdfa2b",
  "headerTemplate": { "left": "{title}", "right": "{date}" },
  "footerTemplate": { "center": "Page {page} / {pages}" }
}

Security: --layout paths are validated against directory traversal, and any attachments[].data field embedded in the JSON is stripped on load. Binary attachment payloads must come from --attachment <path> so the CLI can apply the same path-validation rules.


Command reference#

pdfnative render#

Renders a JSON document into a PDF. Supports both renderer variants exposed by pdfnative.

Core flags#

Flag Default Description
--input <file> stdin JSON file (DocumentParams for --variant document, PdfParams for --variant table)
--output <file> stdout Output PDF path
--variant document|table document Selects buildDocumentPDFBytes (free-form) or buildPDFBytes (table-centric)
--stream off Streaming output via buildDocumentPDFStream (AsyncGenerator<Uint8Array>) — recommended for >100-page documents
--stream-page-by-page off Streaming chunked at PDF object boundaries. Assembles the full document first, so TOC blocks and {pages} are supported. Mutually exclusive with --stream
--stream-true (v1.1.0) off Streaming via buildDocumentPDFStreamTrue / buildPDFStreamTrue — PDF parts are emitted and freed as they go, so the joined binary never materialises. Byte-identical to the buffered builders. Same constraints as --stream (no TOC, no {pages}); mutually exclusive with the other --stream* flags
--max-blocks <n> (v1.1.0) 100000 Exposes layout.maxBlocks so very large multi-thousand-page reports no longer hit a spurious ceiling
--layout <file.json> Load any subset of PdfLayoutOptions
--chunk-size <n> (v1.4.0) 65536 Chunk size in bytes for the --stream / --stream-true output (not applicable to --stream-page-by-page)
--strict (v1.4.0) off Escalate PDF/A diagnostics (PDFA_NO_FONT_ENTRIES, PDFA_UNEMBEDDED_FORM_FONT, PDFA_DEVICE_CMYK_IMAGE) into a hard failure before the first output byte — exit 1, E_CHECK_FAILED. Without it they are warning: lines on stderr (hidden by --quiet) plus an additive diagnostics[] array in the --json envelope

Smart tables (v1.3.0, document variant)#

These flags fill TableBlock fields left unset in the JSON (caption is per-table — set it in the JSON TableBlock):

Flag Description
--table-wrap <mode> auto (default), always, or never
--repeat-header [true|false] Repeat the header row on continuation pages
--zebra [true|false|"R G B"] Alternate-row striping
--min-row-height <pt> Minimum row height in points
--cell-padding <pt> Horizontal cell padding in points

Page geometry#

Flag Default Description
--page-size <name|WxH> a4 Named (a4, letter, legal, a3, tabloid, a5) or WxH in points
--margin <N> or <t,r,b,l> 50 Uniform or per-side margin in points
--compress off Apply /Filter /FlateDecode to all content streams

PDF/A conformance#

Flag Default Description
--tagged <level> none Unified PDF/A flag: none, pdfa1b, pdfa2b, pdfa2u, pdfa3b
--conformance <level> Deprecated. Maps to --tagged pdfa<level> with a one-line stderr notice. Removed in v1.0.0

Watermarks#

Flag Description
--watermark-text <str> Diagonal text watermark
--watermark-image <path> Image watermark (PNG/JPEG, centered, aspect-preserved)
--watermark-opacity <0..1> ExtGState /ca value
--watermark-angle <deg> Rotation angle
--watermark-color <hex|R,G,B> Fill color (text only)
--watermark-font-size <pt> Font size (text only)
--watermark-position background|foreground Drawing order vs. content

Watermarks with transparency are mutually exclusive with PDF/A-1b (ISO 19005-1 §6.4). The pdfnative core enforces this at render time — the run fails with the generic exit code 1 (Watermark transparency is not allowed with PDF/A-1b), not a CLI usage error.

Headers / footers#

Flag Description
--header-left <str> / --header-center / --header-right Page-template zones
--footer-left <str> / --footer-center / --footer-right Page-template zones

Supported placeholders: {page}, {pages}, {date}, {title}. The {pages} placeholder is rejected with --stream because the total page count is only known after multi-pass pagination.

Encryption#

Since v1.3.0, render speaks the same unified encryption vocabulary as merge / split / extract:

Flag Env var Description
--encrypt [aes-128|aes-256] Enable encryption (bare --encrypt = aes-128)
--owner-password <pass> PDFNATIVE_ENCRYPT_OWNER_PASS Required whenever encryption is requested
--user-password <pass> PDFNATIVE_ENCRYPT_USER_PASS Optional password needed to open the document
--permissions <list> Comma list: print, copy, modify, extract

The legacy v0.2.0 flags — --encrypt-algorithm, --encrypt-owner-pass, --encrypt-user-pass, --encrypt-permissions — remain accepted as aliases; when both are given, the unified value wins.

Env vars take precedence over flags, ensuring secrets never appear in shell history. Encryption is mutually exclusive with --tagged pdfa* per ISO 19005-1 §6.3.2 — rejected with exit 2.

PDF/A-3 attachments#

Flag Description
--attachment <path>[:mime[:rel[:desc]]] Embed a file as /EmbeddedFile. Repeatable

The Windows drive-letter colon (D:\path) is detected and not split — see Troubleshooting.

Multilingual fonts#

Flag Description
--lang <code,code> Activate font loaders for the listed languages (e.g. th,ja,ar,te,si,km)
--font <name> (v1.1.0) Register a bundled pdfnative font shortcut. Repeatable. Allow-list covers every bundled font: latin, emoji, color-emoji, math (v1.2.0), and the 22 script codes (ar hy bn ru hi am ka el he ja km ko my pl zh si ta te th bo tr vi). Each shortcut name doubles as its --lang code

--lang activates a programmatically registered font loader via loadFontData(code). Latin scripts are built-in. With v1.1.0, every bundled font is registrable directly through --font — no wrapper script needed for the 22 bundled scripts, colour emoji, or Latin. pdfnative routes each code point to the font whose cmap covers it. See Recipes → Multilang fonts only for fonts you ship yourself (those require calling the library directly).

Bookmarks, math & layout tooling (v1.2.0)#

Flag Description
--outline auto|<tree.json> Add a navigable PDF bookmark tree (/Outlines). auto derives it from the document's headings; a file supplies an explicit OutlineItem[] tree
--font math Register the bundled Noto Sans Math font; pdfnative auto-routes math-operator and geometric-shape code points to it
--inspect-layout Emit a LayoutInspection JSON report (per-page blocks, positions, sizes) instead of a PDF (document variant only)
--debug-layout [margins,content,cells] Render a normal PDF with the opt-in layout-debug guides overlaid
# Bookmarks derived from headings + math font
pdfnative render --input paper.json --output paper.pdf --outline auto --font math

# Layout introspection instead of a PDF
pdfnative render --input report.json --inspect-layout > layout.json

Everything here rides on the pdfnative 1.7.0 engine and is reachable through the existing --layout file and the document JSON — no new flags needed:

Iteration helpers (v0.3.0)#

Flag Description
--watch Re-render on input file change. 200 ms debounce, stderr-only logs. Requires --input <file> and a file --output (stdin / stdout pipelines are not supported — watch needs a stable on-disk source)
--template <file.json> Deep-merge a base template under stdin / --input. Plain objects merge recursively; arrays and primitives are replaced (caller wins). Useful for centralising title / layout / headers in CI
# Watch a file
pdfnative render --input report.json --output report.pdf --watch

# Template + override (template carries title/layout/headers, stdin overrides body)
echo '{"blocks":[{"type":"paragraph","text":"Today only."}]}' \
  | pdfnative render --template template.json -o today.pdf

# Bundled fonts via flag (no wrapper)
echo '{"blocks":[{"type":"paragraph","text":"Hi \ud83d\ude80"}]}' \
  | pdfnative render --font latin --font emoji --lang latin,emoji -o out.pdf

pdfnative sign#

Applies a CMS/PKCS#7 digital signature to an existing PDF.

Flag Default Description
--input <file> stdin Input PDF
--output <file> stdout Output signed PDF
--key <file> PDFNATIVE_SIGN_KEY env PEM-encoded private key (env var takes precedence)
--cert <file> PDFNATIVE_SIGN_CERT env PEM-encoded X.509 certificate (env var takes precedence)
--cert-chain <file> PDFNATIVE_SIGN_CHAIN env Intermediate-CA PEM (repeatable, concatenated into certChain[])
--algorithm <algo> rsa-sha256 rsa-sha256 or ecdsa-sha256 (both fully wired in v0.3.0; SEC1 / PKCS#8 P-256 keys accepted)
--reason <str> PdfSignOptions.reason
--name <str> PdfSignOptions.name
--location <str> PdfSignOptions.location
--contact <str> PdfSignOptions.contact
--signing-time <ISO 8601> now Explicit timestamp; validated up-front before any credential I/O
--pure-crypto (v1.2.0) off Force pdfnative's portable pure-JS bignum CMS path instead of the default native provider
--timestamp <tsa-url> (v1.4.0) Embed an RFC 3161 timestamp token from the given TSA (PAdES B-T — combine with --profile pades for a true ETSI baseline). The only network opt-in on sign — SSRF-guarded, no fallback: transport failure is E_NETWORK, a malformed response is E_PARSE. --dry-run never touches the network
--timestamp-digest <algo> (v1.4.0) sha256 TSA digest: sha256, sha384, or sha512
--timestamp-nonce <hex> (v1.4.0) random Explicit RFC 3161 nonce (testing / reproducibility)
--digest <algo> (v1.4.0) sha256 CMS digest for RSA keys: sha256, sha384, or sha512. Combining sha384/sha512 with --algorithm ecdsa-sha256 is a usage error (exit 2) — ECDSA is SHA-256 only
--profile <p> (v1.4.0) pkcs7 pkcs7 or padespades emits ETSI.CAdES.detached with the ESS signing-certificate-v2 attribute and omits signing-time
--allow-multiple (v1.4.0) off Add a signature to an already-signed PDF instead of refusing (the 1.x idempotent default is preserved)
--field-name <name> (v1.4.0) auto Explicit signature field name (auto-suffixed on collision)
--signature-rect <x1,y1,x2,y2> / --signature-page <n> (v1.4.0) invisible Place a visible signature widget
--placeholder-bytes <n> (v1.4.0) auto Reserve a larger /Contents placeholder (e.g. for long chains or large timestamp tokens)

Native constant-time signing (v1.2.0). sign now routes CMS signing through Node's node:crypto by default (via createNativeCryptoProvider), for side-channel-resistant RSA/ECDSA. Pass --pure-crypto to select the portable pure-JS path (e.g. on a runtime without node:crypto).

PAdES B-T (v1.4.0). --timestamp <tsa-url> — previously a reserved flag that failed with E_UNSUPPORTED — is now functional: the CLI POSTs an RFC 3161 request through its SSRF guard, verifies the token, and embeds it in the CMS unsigned attributes via signPdfBytesWithTimestamp. Pair it with --profile pades: the default pkcs7 profile writes /SubFilter /adbe.pkcs7.detached, a valid CMS signature but not an ETSI EN 319 142-1 baseline. The typical ladder:

pdfnative sign --timestamp <tsa> --profile pades   # → B-T
pdfnative ltv add --online                         # → B-LT
pdfnative doc-timestamp --url <tsa>                # → B-LTA
pdfnative ltv add --online                         # LTV for the doc-timestamp itself

Continue with ltv (B-LT) and doc-timestamp (B-LTA) below.

Signing keys are never logged — not in error output, not in debug traces, not in stack traces. The CLI redacts them at every code path that surfaces error context.

pdfnative inspect#

Inspects metadata and conformance of an existing PDF. Read-only — never modifies the input.

Flag Default Description
--input <file> stdin Input PDF
--format <fmt> json json or text
--verbose off Adds verbose.{trailerKeys, catalogKeys, objectCount, xmpMetadata}. Sanitised — no raw stream bytes
--pages off Adds pages: [{ index, width, height, rotation, annotations, formFields }] — since v1.4.0 also cropBox / trimBox / bleedBox / artBox and userUnit when present. (metadata.trapped is reported unconditionally since v1.4.0, with or without --pages)
--pdfua (v1.1.0) off Adds a pdfua: { valid, errors, warnings } report from validatePdfUA() (ISO 14289-1 structural checks: MarkInfo, StructTree, ParentTree, Lang, per-page MCID uniqueness)
--annotations (v1.2.0) off Lists markup + link annotations per page (from getAnnotations()). /PageLabels are reported automatically when present
--form-fields (v1.3.0) off Lists AcroForm fields (name, type, value, required/read-only)
--encryption (v1.3.0) off Reports the encryption scheme (algorithm, revision, opened-as)
--password <pass> (v1.3.0) Password for an encrypted PDF (env PDFNATIVE_PASSWORD)
--signatures (v1.4.0) off Structural signature inventory via listSignatures()fieldName, subFilter, byteRange, isDocTimestamp, isPlaceholder, sigObjNum, contentsLength. Never emits the signature bytes themselves
--check <assertion> Repeatable; ANDed. Values: pdfa, signed, encrypted, pdfua (v1.1.0), signatures>=N (v1.4.0). Sets exit 0 = pass, 1 = fail
--summary (v1.1.0) off Under --json, emit a canonical minimal verdict ({ pages, encrypted, signatures, pdfa })
--fields <a,b.c> (v1.1.0) Project the JSON result to named dot-paths (array segments map over elements; unknown paths omitted)
--pretty off Force indented JSON even under the global --json (agent mode is compact)

Composable example:

pdfnative inspect --input dist/q1.pdf \
  --check pdfa --check signed \
  --format json > dist/q1.report.json
echo "exit code: $?"   # 0 if both assertions hold

Behaviour change in v1.4.0: --check signed now counts only actual signatures — unsigned placeholders and /DocTimeStamp revisions no longer satisfy it. A placeholder-only PDF that passed under v1.3.0 fails under v1.4.0; this is a deliberate correctness fix. v1.4.0 also fixes the signatures and formFields counters in the JSON body, which previously always reported 0. Use --check "signatures>=1" together with --signatures when you need the structural inventory.

pdfnative verify#

Verifies CMS/PKCS#7 signatures embedded in a PDF.

Flag Default Description
--input <file> stdin Input PDF
--format <fmt> json json or text
--strict off Exit 1 on any failure or zero signatures
--trust <pem> Trust-anchor certificate (repeatable)
--revocation <mode> offline offline (embedded /DSS only), online (opt-in, SSRF-guarded OCSP/CRL fetch), or disabled
--revocation-policy <p> soft-fail soft-fail or strict
--summary (v1.1.0) off Under --json, emit a minimal verdict ({ valid, signatures, invalid })
--fields <a,b.c> (v1.1.0) Project the JSON result to named dot-paths
--pretty off Indented JSON even under the global --json

Scope (since v1.0.0):

Since v1.4.0 the CLI also creates LTV material — sign --timestamp (B-T), ltv (B-LT /DSS), and doc-timestamp (B-LTA) — so verify and the write side now cover the same PAdES ladder.

pdfnative ltv (v1.4.0)#

Embeds long-term-validation material — certificates, OCSP responses, CRLs — into a signed PDF's /DSS dictionary (PAdES B-LT), via the engine's collectValidationInfo / embedValidationInfo / addValidationInfo.

# Connected machine: gather the revocation evidence as replayable JSON
pdfnative ltv collect --input signed.pdf --online --output ltv-data.json

# Air-gapped machine: embed it — no network I/O is even possible here
pdfnative ltv embed --input signed.pdf --data ltv-data.json --output signed.lt.pdf

# Or both in one pass on a connected machine
pdfnative ltv add --input signed.pdf --online --output signed.lt.pdf
Flag Default Description
collect | embed | add (required) Subcommand: gather evidence, embed evidence, or both
--input <file> stdin Signed source PDF
--output <file> stdout Destination — collect writes the evidence JSON, embed/add write the PDF
--online off Required for collect / add — the explicit network opt-in (refused with exit 2 without it). embed is 100 % offline by design
--data <file> Evidence JSON produced by collect (for embed)
--prefer <src> ocsp or crl when both are available
--extra-cert <pem> Additional certificate for the /DSS (repeatable)
--timeout <ms> 10000 Per-fetch network timeout
--dry-run off Validate without writing

The evidence JSON (schema ltv-data, versioned, DER as base64) is the air-gap bridge: collect on a connected machine, transfer the JSON, embed inside the enclave. Network fetches go through the same SSRF guard as verify --revocation online; failures surface as E_NETWORK.

pdfnative doc-timestamp (v1.4.0)#

Appends an RFC 3161 document timestamp revision (/DocTimeStamp, /SubFilter /ETSI.RFC3161 — ISO 32000-2 §12.8.5) covering every byte of the document (PAdES B-LTA). Earlier revisions stay byte-identical; run it again years later to renew the protection before the TSA's certificate expires.

pdfnative doc-timestamp --input signed.lt.pdf \
  --url https://tsa.example.com/rfc3161 --output signed.lta.pdf
Flag Default Description
--input <file> stdin Source PDF
--output <file> stdout Output PDF
--url <tsa-url> (required) TSA endpoint — requiring it makes the network use an explicit opt-in
--digest <algo> sha256 sha256, sha384, or sha512
--field-name <name> DocTimeStamp1 Field name (auto-suffixed on collision)
--placeholder-bytes <n> 12288 Reserved /Contents size for the token
--nonce <hex> random Explicit RFC 3161 nonce
--timeout <ms> 10000 Network timeout
--dry-run off Validate without writing (never touches the network)

pdfnative merge (v1.2.0)#

Concatenates 2–50 PDFs into one document, in order, via mergePdfs.

pdfnative merge a.pdf b.pdf c.pdf --output combined.pdf
Flag Default Description
<paths…> / --input <file> (required) Source PDFs — positional paths and/or repeatable --input. 2–50 total
--output <file> stdout Output PDF
--drop-annotations off Drop non-link annotations from the sources
--max-output-size <bytes> 256 MiB Reject an output larger than this ceiling
--password <pass> (v1.3.0) Password for encrypted sources (env PDFNATIVE_PASSWORD). One password is applied to every source; mixed passwords fail with E_PASSWORD
--encrypt [aes-128|aes-256] (v1.3.0) Re-encrypt the output (bare = aes-128); needs --owner-password
--owner-password / --user-password (v1.3.0) Passwords for --encrypt (env PDFNATIVE_ENCRYPT_OWNER_PASS / _USER_PASS)
--permissions <list> (v1.3.0) print,extract Comma list: print, copy, modify, extract — without the flag, print and extract are allowed, copy and modify denied
--stream (v1.3.0) off Constant-memory streaming output (--chunk-size N)
--dry-run off Validate inputs without writing output

Encrypted sources are supported since v1.3.0 via --password (a single password applied to every source), and the output can be re-encrypted via --encrypt. Signatures and /AcroForm are dropped (page edits invalidate /ByteRange); self-contained URI /Link annotations are preserved. Every path — positionals included — is validated against traversal.

pdfnative split (v1.2.0)#

Splits one PDF into many via splitPdf — one output per page (default) or one per comma-separated range.

# One output per page
pdfnative split --input report.pdf --output-dir pages/ --prefix page

# One output per range (1-based, inclusive)
pdfnative split --input report.pdf --output-dir out/ --pages "1-2,3-4"
Flag Default Description
--input <file> stdin Source PDF
--output-dir <dir> (required) Destination directory; parts are written as <prefix>-<n>.pdf (zero-padded)
--prefix <str> input basename (part on stdin) Output filename prefix
--pages <ranges> per-page Comma-separated 1-based inclusive ranges (e.g. 1-2,3-4)
--max-output-size <bytes> 256 MiB Per-part output ceiling
--password <pass> (v1.3.0) Password for an encrypted source (env PDFNATIVE_PASSWORD)
--encrypt [aes-128|aes-256] (v1.3.0) Re-encrypt each output (needs --owner-password; --user-password / --permissions as in merge)
--stream (v1.3.0) off Constant-memory streaming output (--chunk-size N)
--dry-run off Validate without writing

pdfnative extract (v1.2.0)#

Pulls a selected, order-preserving subset of pages into a single PDF via extractPages.

pdfnative extract --input report.pdf --output cover.pdf --pages "4,1-2"
Flag Default Description
--input <file> stdin Source PDF
--output <file> stdout Output PDF
--pages <list> (required) 1-based page list/ranges; order is preserved and repeats are allowed
--max-output-size <bytes> 256 MiB Output ceiling
--password <pass> (v1.3.0) Password for an encrypted source (env PDFNATIVE_PASSWORD)
--encrypt [aes-128|aes-256] (v1.3.0) Re-encrypt the output (needs --owner-password; --user-password / --permissions as in merge)
--stream (v1.3.0) off Constant-memory streaming output (--chunk-size N)
--dry-run off Validate without writing

pdfnative annotate (v1.2.0)#

Attaches markup annotations to an existing PDF via an incremental save, so the original bytes — and any existing signature — stay intact.

pdfnative annotate --input report.pdf --output annotated.pdf \
  --annotations notes.json

notes.json is a JSON array (or { "annotations": […] }), each entry a markup annotation plus a 1-based page:

[
  { "page": 1, "type": "highlight", "rect": [72, 700, 520, 715], "color": "#ffe066", "contents": "Review this clause" },
  { "page": 2, "type": "text", "rect": [80, 640, 100, 660], "contents": "Sticky note" }
]

Supported types: text, highlight, underline, strikeout, squiggly, square, circle, line, freetext. Only known fields are forwarded (no dictionary injection). Read them back with inspect --annotations. Since v1.4.0, --password <pass> (env PDFNATIVE_PASSWORD) lets you annotate an encrypted PDF — the added objects are encrypted under the document's existing scheme.

Overlay, not redaction. Annotations are a visual review layer; the underlying bytes remain. They do not remove or obscure content for security purposes.

pdfnative metadata (v1.4.0)#

Updates /Info and XMP metadata via an incremental save — the original bytes are kept as a prefix, so existing signatures remain valid for their revision. xmp:ModifyDate and pdf:Keywords stay synchronised. Reading metadata remains inspect's job.

pdfnative metadata --input report.pdf --output revised.pdf \
  --title "Quarterly report (revised)" --keywords "finance,Q2"
Flag Default Description
--input <file> stdin Source PDF
--output <file> stdout Output PDF
--title / --author / --subject / --keywords Individual /Info + XMP fields (at least one field is required)
--mod-date <ISO 8601> now Explicit modification date
--from-json <file> All fields from a JSON file (schema metadata) — mutually exclusive with the individual flags
--password <pass> Open an encrypted source (env PDFNATIVE_PASSWORD)
--dry-run off Validate without writing

pdfnative govern (v1.2.0)#

Surfaces pdfnative's AI-governance / Human-in-the-Loop (HITL) contract. Agents act as draftsmen: a human must always review and submit under their own GitHub identity.

pdfnative govern rules                  # human/agent protocol (AGENT_RULES)
pdfnative govern policy --json          # machine-readable policy JSON
pdfnative govern verify-issue draft.md  # gate a draft (exit 1 / E_POLICY)
Subcommand Description
rules Print the human-and-agent protocol (mirrors .github/AGENT_RULES.md)
policy [--json] Print the governance policy (mirrors .github/ai-governance.json)
verify-issue <draft.md> Gate a local issue draft; exit 1 / E_POLICY on a violation (proposes a runtime dependency, omits a reproduction code block). Missing recommended fields surface as warnings

verify-issue is a pure, fully offline validator — no GitHub or network access. A passing check is necessary but not sufficient: the human review gate always applies.

pdfnative fill (v1.3.0)#

Fill, flatten or export an AcroForm. The three modes compose into a round trip: export the current values, edit the JSON, feed it back.

pdfnative fill --input form.pdf --export --output values.json   # read
pdfnative fill --input form.pdf --data values.json --output filled.pdf
pdfnative fill --input filled.pdf --flatten --output flat.pdf   # freeze
Flag Default Purpose
--data <file> Values JSON. Required unless --flatten or --export
--flatten off Flatten after filling, or flatten the existing values
--export off Read-only: emit current values in --data shape
--force off Flatten even when a signed signature field is present
--on-unknown <mode> throw throw or ignore for unknown field names
--need-appearances off Set /NeedAppearances to allow non-WinAnsi values
--password <pass> Open an encrypted form (env PDFNATIVE_PASSWORD)
--dry-run off Validate and enumerate fields without writing

Flattening a signed document invalidates the signature, which is why --force exists rather than it being silent.

pdfnative encrypt (v1.3.0)#

Re-secure an existing PDF. --owner-password is required; without a --user-password the document opens with no prompt and the owner password only governs permissions.

pdfnative encrypt --input report.pdf --output secure.pdf \
  --owner-password "$OWNER" --user-password "$USER" \
  --algorithm aes-256 --permissions print,extract
Flag Default Purpose
--owner-password <pass> Required. Env PDFNATIVE_ENCRYPT_OWNER_PASS
--user-password <pass> Password needed to open. Env PDFNATIVE_ENCRYPT_USER_PASS
--algorithm <alg> aes-128 aes-128 or aes-256
--permissions <list> print,extract Comma-separated: print, copy, modify, extract — without the flag, print and extract are allowed, copy and modify denied
--password <pass> Open an already-encrypted source (password rotation)
--drop-annotations off Drop all annotations; the default keeps URI links
--max-output-size <n> 256 MiB Cap on the assembled size
--stream off Stream the output (--chunk-size N)
--dry-run off Validate without writing

pdfnative decrypt (v1.3.0)#

Remove encryption, given the password. Reads RC4, AES-128 and AES-256 sources.

pdfnative decrypt --input secure.pdf --password "$PASS" --output plain.pdf
Flag Default Purpose
--password <pass> Document password. Env PDFNATIVE_PASSWORD
--drop-annotations off Drop all annotations; the default keeps URI links
--max-output-size <n> 256 MiB Cap on the assembled size
--stream off Stream the output (--chunk-size N)
--dry-run off Validate without writing

pdfnative extract-text (v1.3.0)#

Reading-order Unicode text from an existing PDF. ndjson emits one JSON object per page, which makes it a natural feed for a RAG ingestion pipeline.

pdfnative extract-text --input paper.pdf --format ndjson --runs > pages.ndjson
pdfnative extract-text --input paper.pdf --pages 1,3,5-7 --format text
Flag Default Purpose
--format, -f <fmt> text text, json, or ndjson (one object per page)
--pages <selector> all 1-based selector, e.g. 1,3,5-7
--runs off Include positioned runs { text, x, y, fontSize, fontName }
--password <pass> Extract from an encrypted PDF
--max-length <n> 16000000 Hard cap on total characters; 0 disables
--summary off (json) Emit only { pages, characters }
--fields <paths> (json) Comma-separated dot-paths to keep

Text comes from the /ToUnicode mapping, so it is real Unicode rather than glyph indices. A page whose content decodes entirely to U+FFFD is reported as not extractable rather than returning noise.

pdfnative compare (v1.4.0)#

Compares two PDFs by extracted text and by structure — page count, page/print boxes, metadata, form fields, annotations, encryption, signatures. Built for CI: identical documents exit 0; differences print the report on stdout first, then exit 1 with E_CHECK_FAILED.

pdfnative compare golden.pdf candidate.pdf --mode both --format json --pretty
Flag Default Description
<a.pdf> <b.pdf> (required) The two documents, as positionals
--mode <m> both text, structure, or both
--format <fmt> text text or json
--tolerance <pt> 0 Geometric tolerance in points for box comparisons
--ignore-whitespace off Normalise whitespace before the text diff
--pages <selector> all 1-based page selector
--password-a / --password-b Passwords for encrypted inputs
--pretty off Indented JSON

Not a visual diff. pdfnative has no rasteriser, so pixel comparison is out of scope by design — compare diffs what the format itself declares (text and structure). Pair it with an external rasteriser if you need pixel-level checks.

pdfnative doctor (v1.3.0)#

Environment and capability preflight. The first thing to run in a new environment, and the first thing an agent should call before planning work.

pdfnative doctor              # human-readable
pdfnative doctor --format json --pretty
Flag Default Purpose
--format, -f <fmt> text text or json
--pretty off Indented JSON even under the global --json

pdfnative batch#

Renders every JSON file in a directory to PDF in parallel, reusing the full render pipeline.

Flag Default Description
--input-dir <dir> (required) Directory of *.json documents
--output-dir <dir> (required) Destination directory for *.pdf (created if absent)
--concurrency <n> 4 Bounded parallelism
--fail-fast off Abort on the first failure
--dry-run (v1.1.0) off Validate every input without writing output
--json / --summary (v1.1.0) off Machine-readable per-file report; --summary emits { total, succeeded, failed }
--format <fmt> text Report format: text or json
[render options] --layout, --variant, smart tables, PDF/A, compression… — every render option is honoured and applied to each file
--manifest <tasks.json> (v1.4.0) Declarative pipeline mode — see below. Mutually exclusive with --input-dir / --output-dir (exit 2)
--allow-network (v1.4.0) off Required before any network flag inside a manifest (--timestamp, --url, --online, --revocation online) — an untrusted manifest can never trigger network I/O on its own
--continue-on-error (v1.4.0) off Keep going after a task fails; tasks that reference the failed output via @id are skipped
pdfnative batch --input-dir inputs/ --output-dir outputs/ --tagged pdfa2b --concurrency 4

Manifest mode (v1.4.0)#

--manifest tasks.json runs a declarative pipeline — { "version": 1, "tasks": [{ "id", "command", "flags" }] } (schema batch-manifest). Flag names are written bare, without the leading dashes (a dashed key is rejected with exit 2). A flag value of "@<id>" references the output of an earlier task; relative paths resolve against the manifest's directory. The whole manifest is validated before anything runs, then executed sequentially and fail-fast (unless --continue-on-error).

{
  "version": 1,
  "tasks": [
    { "id": "report",  "command": "render", "flags": { "input": "report.json", "output": "report.pdf", "tagged": "pdfa2b" } },
    { "id": "stamped", "command": "metadata", "flags": { "input": "@report", "output": "final.pdf", "title": "Q2 report" } }
  ]
}

Only a fixed 14-command whitelist is allowed inside a manifest — render, sign, verify, inspect, merge, split, extract, extract-text, fill, encrypt, decrypt, annotate, metadata, doc-timestamp. ltv and compare are excluded (they take positionals, which manifest tasks — a flat flag map — cannot express), and the meta commands (batch, govern, schema, completion, doctor) can never nest. Manifests are capped at 1 000 tasks with the same path-traversal checks as direct flags. Error classes: a structural violation, an invalid flag name or value type, or a network-reaching flag without --allow-network exits 2/E_USAGE; an invalid or duplicate task id, a non-whitelisted command, or a broken @ref exits 1/E_INPUT; malformed JSON is 1/E_PARSE.

pdfnative schema#

Prints a versioned JSON Schema (Draft 2020-12) for a CLI input/output shape, so agents can self-validate before invoking a command. The $id embeds the CLI version.

pdfnative schema render            # input schema for `render`
pdfnative schema inspect-summary   # compact inspect verdict shape
pdfnative schema list              # enumerate every subject

Subjects (19): render, inspect, verify, batch, annotate (v1.2.0), govern-verify (v1.2.0), extract-text, fill, form-export, status, manifest, doctor (all v1.3.0), metadata, ltv-data, compare, batch-manifest (all v1.4.0), and the compact inspect-summary / verify-summary / batch-summary shapes. pdfnative schema list enumerates them all.

pdfnative completion#

Emits a shell-completion script: pdfnative completion bash|zsh|fish|powershell (PowerShell added in v1.3.0).


Agent-native automation contract#

v1.1.0 makes the CLI deterministic to drive from autonomous AI agents and CI pipelines. The full contract is documented in pdfnative-cli AGENTS.md.

# Agent-friendly: compact verdict on stdout, structured status on stderr
pdfnative inspect --input report.pdf --json --summary 2>status.json

# Branch on a stable error class
pdfnative verify --input report.pdf --json --strict \
  || echo "failed: $(jq -r .error.code status.json)"

Recipes#

Render → sign → verify → inspect, in a single chain#

cat report.json \
  | pdfnative render --tagged pdfa2b --compress \
  | pdfnative sign --reason "Approved" \
  | tee signed.pdf \
  | pdfnative verify --strict --trust ca.pem
pdfnative inspect --input signed.pdf --check pdfa --check signed

Encrypted PDF/A-3 hybrid invoice (Factur-X / ZUGFeRD)#

pdfnative render \
  --input invoice.json --output invoice.pdf \
  --tagged pdfa3b \
  --attachment factur-x.xml:application/xml:Source:"Structured invoice data" \
  --footer-center "Page {page} / {pages}"

Encrypted distribution copy#

pdfnative render \
  --input contract.json --output contract.encrypted.pdf \
  --encrypt-algorithm aes256 \
  --encrypt-permissions print
# PDFNATIVE_ENCRYPT_OWNER_PASS read from the env — never on the command line

Multilang fonts#

For the 22 bundled scripts (plus latin, emoji, color-emoji, math), no wrapper is needed — --font registers each bundled font inside the CLI process:

echo '{"blocks":[{"type":"paragraph","text":"สวัสดี こんにちは"}]}' \
  | pdfnative render --font th --font ja --lang th,ja -o out.pdf

For a font you ship yourself (not bundled), the CLI cannot use it: in-memory font registration does not cross a process boundary, so a wrapper that registers fonts and then spawns the CLI does not work. Call the library directly instead:

// render-custom-font.mjs
import { registerFont, loadFontData, buildDocumentPDFBytes } from 'pdfnative';
import { writeFile } from 'node:fs/promises';

registerFont('th', () => import('./fonts/my-thai-data.js'));
const th = await loadFontData('th');
if (!th) throw new Error('Thai font failed to load');
const bytes = buildDocumentPDFBytes({
  title: 'Thai demo',
  blocks: [{ type: 'paragraph', text: 'สวัสดี' }],
  fontEntries: [{ fontData: th, fontRef: '/F3', lang: 'th' }], // /F1 and /F2 are reserved
});
await writeFile('out.pdf', bytes);

CI assertion (GitHub Actions)#

- name: Render and assert PDF/A + signed
  run: |
    pdfnative render --input data/q1.json --output dist/q1.pdf --tagged pdfa2b
    pdfnative sign  --input dist/q1.pdf  --output dist/q1.signed.pdf
    pdfnative verify --input dist/q1.signed.pdf --strict
    pdfnative inspect --input dist/q1.signed.pdf --check pdfa --check signed

Batch-render a directory of JSON files#

for f in inputs/*.json; do
  pdfnative render --input "$f" --output "outputs/$(basename "$f" .json).pdf" --tagged pdfa2b
done

Security model#

pdfnative-cli is built with the same zero-trust posture as the underlying library:

The CLI opens no network connection unless you explicitly opt in — the only network paths are verify --revocation online, sign --timestamp <url>, ltv --online, doc-timestamp --url <url>, and batch --allow-network, and all of them go through the same SSRF guard: http/https only, private / loopback / metadata addresses blocked, DNS pinning, 10 s timeout, 5 MiB response cap, no redirects. Response bodies are never echoed into CLI output. It never writes to system directories outside the working directory or loads arbitrary code.


Comparison with the library API#

The CLI now covers nearly the full library surface; only Web Worker offloading remains library-only.

Feature CLI v1.4.0 Library
Document rendering (13 block types)
Streaming output --stream / --stream-true buildDocumentPDFStream() / buildDocumentPDFStreamTrue()
Configurable block cap --max-blocks layout.maxBlocks
PDF/A conformance (1b, 2b, 2u, 3b) --tagged tagged: '…'
Digital signatures (RSA-SHA256/384/512) ✅ (--digest, v1.4.0 for 384/512) signPdfBytes()
Digital signatures (ECDSA-SHA256) --algorithm ecdsa-sha256 signPdfBytes()
PAdES B-T timestamp at signing sign --timestamp (v1.4.0) signPdfBytesWithTimestamp()
PAdES B-LT /DSS (LTV) ltv collect / embed / add (v1.4.0) collectValidationInfo() / embedValidationInfo() / addValidationInfo()
PAdES B-LTA document timestamp doc-timestamp (v1.4.0) addDocumentTimestamp()
Metadata update (signature-safe) metadata (v1.4.0) PdfModifier.updateMetadata()
Text + structure diff compare (v1.4.0 — implemented in the CLI) — (compose the readers)
Native constant-time signing ✅ default (--pure-crypto opts out; the native provider is a CLI utility) setCryptoProvider() (accepts any CryptoProvider)
Inspection / metadata PdfReader
PDF/UA structural validation inspect --pdfua validatePdfUA()
Annotation listing inspect --annotations getAnnotations()
Signature verification (CMS/PKCS#7) verify (real CMS, RSA + ECDSA — implemented in the CLI) openPdf() + verifyCertSignature() only (X.509 certificate-signature check, not CMS PDF-signature verification)
PAdES-T timestamp + OCSP/CRL revocation verify --revocation (implemented in the CLI)
Encryption (AES-128/256) --encrypt-* encryption: {…}
Watermarks --watermark-* watermark: {…}
PDF/A-3 attachments --attachment attachments: [...]
22 scripts + COLRv1 emoji + math --font / --lang registerFont() / loadFontData()
Page-tree editing (merge / split / extract) merge / split / extract mergePdfs() / splitPdf() / extractPages()
Markup annotations annotate PdfModifier.addAnnotation() / buildAnnotationBody()
Bookmarks / outline render --outline outline: '…'
Layout introspection / debug render --inspect-layout / --debug-layout inspectDocumentLayout() / layout.debug
AI-governance / HITL gate govern (draft validation via the CLI's validateGovernanceDraft()) — (contract files in the repo; the core's check is a repo script, not a published API)
Parallel batch render batch — (compose render)
JSON Schema export schema — N/A
Agent-native --json/E_*/--dry-run — N/A
Table-centric variant --variant table buildPDFBytes()
Full PdfLayoutOptions --layout file.json
Web Worker offloading ❌ N/A pdfWorker.ts

Examples — ready-to-run#

The samples/ directory in the CLI repository ships 40+ ready-to-run examples organized by feature:

Category What it shows
render/document/ Minimal document, all blocks reference, invoice, technical spec, multi-page report
render/table/ Project status, financial summary
render/table-variant/ PdfParams-shaped financial ledger via --variant table
render/barcode/ QR code, Code 128, EAN-13
render/form/ Contact form, survey
render/toc/ Auto-generated table of contents with /GoTo links
render/link/ Resource directory with hyperlinks
render/watermark/ Draft / Confidential watermarks
render/layout/ US Letter, A5 portrait, A4 landscape
render/pdfa/ PDF/A-1b, 2b, 3b archival conformance
render/encryption/ AES-128 password-protected PDF
render/headers-footers/ Page templates with {page}/{pages}/{date}/{title}
render/attachments/ PDF/A-3 hybrid invoice with embedded XML (Factur-X / ZUGFeRD)
render/multilang/ Multilang rendering via the bundled --font shortcuts
render/chart/ Native vector charts (bar, line, pie, donut) (v1.3.0)
render/math/ Noto Sans Math font routing (v1.2.0)
render/outline/ /Outlines bookmark trees (--outline auto and explicit JSON) (v1.2.0)
render/table-smart/ Smart-table flags (--table-wrap, --zebra, …) (v1.3.0)
render/font/, render/template/, render/watch/, render/inspect-layout/ Font shortcuts, templates, watch mode, layout introspection
fill/ AcroForm fill / flatten / export round trip (v1.3.0)
encrypt/ Encrypt / decrypt an existing PDF (v1.3.0)
extract-text/ Reading-order text extraction (text / json / ndjson) (v1.3.0)
merge/, split/, extract/, annotate/ Page-tree editing and markup annotations (v1.2.0)
sign/ Bash + PowerShell signing scripts (basic + with metadata)
inspect/ JSON & text inspection, --verbose --pages, --check pdfa
verify/ Self-signed verification, strict-mode CI gating
batch/, doctor/, govern/, completion/, agent/, config/ Automation, preflight, governance, shell completion, agent patterns
streaming/ 200-section document via streaming render

Render them all at once:

git clone https://github.com/Nizoka/pdfnative-cli
cd pdfnative-cli
node samples/run-all.js

Release history#

What's new in v1.4.0#

v1.4.0 is built on pdfnative 1.7.0 (pin ^1.7.0) and completes the signature story: the full PAdES ladder (B-B → B-T → B-LT → B-LTA) is now writable from the command line, alongside signature-safe metadata edits, a CI-grade document diff, and a declarative batch pipeline. No contract change — every flag, default, exit code, error code and envelope from 1.x behaves identically on Node ≥ 22.

Area v1.3.0 v1.4.0
Commands 17 commands adds metadata (incremental /Info + XMP edits that keep signatures valid), ltv (PAdES B-LT — collect / embed / add, air-gap-friendly), doc-timestamp (PAdES B-LTA /DocTimeStamp revisions), and compare (text + structure diff with CI exit codes)
Signing --timestamp reserved (E_UNSUPPORTED) sign --timestamp <tsa-url> works (RFC 3161, PAdES B-T) with --timestamp-digest / --timestamp-nonce; plus --digest sha384|sha512 (RSA), --profile pades, --allow-multiple, --field-name, visible signatures (--signature-rect / --signature-page), --placeholder-bytes
Verification RSA/ECDSA-SHA256 adds rsa-sha384 / rsa-sha512 and validates /DocTimeStamp revisions; per-signature fieldName and isDocTimestamp (additive)
Inspection metadata, forms, encryption adds --signatures (structural inventory), --check "signatures>=N", print boxes (cropBox / trimBox / bleedBox / artBox, userUnit), trapped; --check signed no longer counts unsigned placeholders (correctness fix)
render charts v1 (5 types) --strict PDF/A gate, image blocks from JSON, print production (layout.print, output intent, viewer preferences, /Trapped), charts v2 (9 types, dual axes, log/time scales, data labels), --chunk-size
Automation per-directory batch batch --manifest — declarative pipeline with @id references, a 14-command whitelist, --allow-network opt-in and --continue-on-error
Hardening global --max-inflate-size (anti zip-bomb, default 100 MiB); all network paths opt-in behind the SSRF guard
Agent contract 11 codes, 15 subjects E_NETWORK; schema grows to 19 subjects (metadata, ltv-data, compare, batch-manifest); 600 tests on Node 22/24
Compatibility pdfnative ^1.6.0, Node ≥ 20 pdfnative ^1.7.0, Node ≥ 22

Full changelog: pdfnative-cli release notes v1.4.0.

What's new in v1.3.0#

v1.3.0 is built on the pdfnative 1.6 engine (pin ^1.6.0) and surfaces its engine additions as five new commands (extract-text, fill, encrypt, decrypt, doctor), native vector charts in render, and password / re-encryption / constant-memory streaming on the page-tree commands. Fixes a silent render --encrypt no-op. 100 % backward-compatible with v1.2.0.

Area v1.2.0 v1.3.0
Commands 12 commands adds fill (fill / flatten / export AcroForms via an incremental save), encrypt, decrypt, extract-text (reading-order Unicode text: text | json | ndjson), and doctor (offline environment / capability preflight)
Charts the engine's chart document block (bar, barH, line, pie, donut — the pdfnative 1.6 set) renders as pure vector path operators through render
Encryption vocabulary --encrypt-* flags on render only unified vocabulary--encrypt [aes-128|aes-256], --owner-password, --user-password, --permissions print,copy,modify,extract — shared by render, merge, split and extract (legacy --encrypt-* flags kept as aliases)
Page-tree commands plaintext sources only --password reads encrypted sources; --encrypt re-encrypts the output; --stream (+ --chunk-size) streams the output with constant memory
Inspection metadata, PDF/UA, annotations adds inspect --form-fields, --encryption and --password
Shell completion bash, zsh, fish adds powershell
Agent contract --json / E_* / --dry-run / schemas adds the stable E_PASSWORD code and an agent capability manifest (schema manifest + llms.txt)
Compatibility pdfnative ^1.5.0 pdfnative ^1.6.0

Full changelog: pdfnative-cli release notes v1.3.0.

What's new in v1.2.0#

v1.2.0 lands the pdfnative 1.5.0 engine's page-tree and annotation APIs on the CLI as five new commands, adds document bookmarks, a math font, layout introspection, native constant-time signing, and — for autonomous agents — surfaces pdfnative's AI-governance / Human-in-the-Loop (HITL) contract. 100 % backward-compatible with v1.1.0.

Area v1.1.0 v1.2.0
Commands render, sign, inspect, verify, batch, schema adds merge, split, extract (page-tree), annotate (markup), and govern (rules / policy / verify-issue)
Bookmarks render --outline auto derives a /Outlines bookmark tree from headings; --outline <tree.json> supplies an explicit OutlineItem[]
Math font render --font math registers the bundled Noto Sans Math font; pdfnative auto-routes math-operator / geometric-shape code points to it
Layout tooling render --inspect-layout emits a LayoutInspection JSON report; --debug-layout [margins,content,cells] overlays layout guides on a normal PDF
Signing pure-JS bignum CMS native node:crypto by default (constant-time, side-channel-resistant RSA/ECDSA); --pure-crypto opts back into the portable pure-JS path
Inspection metadata, PDF/UA inspect --annotations lists markup + link annotations; /PageLabels are reported automatically when present
Agent / governance --json/E_*/--dry-run adds the stable E_POLICY code; schema gains annotate + govern-verify subjects; --dry-run now also covers merge / split / extract / annotate
Compatibility pdfnative ^1.3.0 pdfnative ^1.5.0

Full changelog: pdfnative-cli release notes v1.2.0.

Previously in v1.1.0#

v1.1.0 is built on pdfnative 1.3.0 and surfaces its new engine capabilities through the CLI, plus a full agent-native automation contract. 100 % backward-compatible with v0.3.0.

Area v0.3.0 v1.1.0
Fonts --font {latin,emoji} 22 Unicode scripts + COLRv1 colour emoji--font/--lang allow-list covers every bundled font (latin, emoji, color-emoji, and the 22 script codes incl. Telugu te, Sinhala si, Tibetan bo, Khmer km, Myanmar my, Amharic am)
Streaming --stream (single-pass), page-by-page adds --stream-true — true constant-memory streaming via buildDocumentPDFStreamTrue / buildPDFStreamTrue; the joined binary never materialises
Block cap hard-coded ceiling --max-blocks <n> exposes layout.maxBlocks (default 100 000) for very large reports
Accessibility PDF/UA (ISO 14289-1) structural validatorinspect --pdfua and --check pdfua as a CI accessibility gate
Agent contract global --json status/error envelope on stderr, stable E_* error codes, --dry-run, token-economy --summary / --fields projection, compact JSON by default
Commands render, sign, inspect, verify adds batch (parallel directory render) and schema (JSON Schema export)
Supply chain provenance adds a CycloneDX SBOM (sbom.cdx.json) attached to every release + OpenSSF Scorecard badge
Compatibility pdfnative ^1.1.0 pdfnative ^1.3.0

Full changelog: pdfnative-cli release notes v1.1.0.

Previously in v0.3.0#

v0.3.0 finished the digital-signature story and added three iteration-friendly render flags. 100 % backward-compatible with v0.2.0.

Area v0.2.0 v0.3.0
sign algorithm RSA-SHA256 only (ECDSA stub) RSA-SHA256 and ECDSA-SHA256 — fully wired via parseEcPrivateKey (SEC1 / PKCS#8 P-256)
sign placeholder Required a prior prepare_signature_placeholder call Auto-injection — CLI detects PDFs with no AcroForm signature field and adds /Sig via a single incremental update
verify scope Byte-range integrity + cert chain Real CMS/PKCS#7 verification — signature value (RSA + ECDSA), message digest, certificate chain, trust roots, RFC 3161 timestamp detection
render --watch Re-render on input change (200 ms debounce, stderr-only logs)
render --template <file.json> Deep-merge a base template under stdin / --input
render --font <name> Bundled font shortcut (latin, emoji)
Compatibility pdfnative ^1.0.5 pdfnative ^1.1.0

Previously in v0.2.0#

The v0.2.0 release expanded the CLI from ~10 flags to a near-complete projection of the pdfnative v1.0.5 surface, while remaining 100 % backward-compatible with v0.1.0.

Area v0.1.0 v0.2.0
Layout --conformance only Hybrid model — high-frequency knobs as flags, full PdfLayoutOptions via --layout file.json
PDF/A --conformance 1b|2b|3b --tagged none|pdfa1b|pdfa2b|pdfa2u|pdfa3b (--conformance deprecated)
Encryption --encrypt-owner-pass, --encrypt-user-pass, --encrypt-algorithm, --encrypt-permissions (env-var precedence)
Watermarks --watermark-text/-image/-opacity/-angle/-color/-font-size/-position
Headers / footers --header-{l,c,r}, --footer-{l,c,r} with {page}/{pages}/{date}/{title} placeholders
PDF/A-3 attachments --attachment <path>[:mime[:rel[:desc]]] (repeatable)
Multilingual fonts --lang th,ja,ar (at the time, required a wrapper registering fonts via registerFont(); superseded by --font in v1.1.0)
verify command n/a byte-range integrity + cert chain + --trust roots

Migration v0.3.0 → v1.1.0#

100 % backward-compatible. Every v0.3.0 invocation continues to produce a byte-equivalent PDF. New, optional opportunities:

# 1. True constant-memory streaming for very large reports
- pdfnative render --input big.json --output big.pdf --stream
+ pdfnative render --input big.json --output big.pdf --stream-true

# 2. CI accessibility gate (PDF/UA)
+ pdfnative inspect --input report.pdf --check pdfua

# 3. Agent-native, token-frugal output
+ pdfnative inspect --input report.pdf --json --summary

# 4. Parallel directory render
+ pdfnative batch --input-dir inputs/ --output-dir outputs/ --concurrency 4

Migration v0.2.0 → v0.3.0#

100 % backward-compatible. Three forward-looking opportunities:

# 1. ECDSA signing now works without a workaround
- pdfnative sign -i in.pdf -o out.pdf --algorithm rsa-sha256 ...
+ pdfnative sign -i in.pdf -o out.pdf --algorithm ecdsa-sha256 \
+   --key ec-key.pem --cert ec-cert.pem

# 2. Sign without a prior placeholder step
- pdfnative sign -i with-placeholder.pdf -o signed.pdf ...
+ pdfnative sign -i any-pdf.pdf -o signed.pdf ...   # placeholder auto-injected

Migration v0.1.0 → v0.2.0#

100 % backward-compatible — every v0.1.0 invocation continues to produce a byte-equivalent PDF, modulo a one-line stderr notice for --conformance. All v0.1.0 exit codes and JSON shapes are preserved; new inspect JSON fields are additive only.

The only soft change you should plan for:

- pdfnative render --input doc.json --output report.pdf --conformance 2b
+ pdfnative render --input doc.json --output report.pdf --tagged pdfa2b

--conformance will be removed in v1.0.0 of the CLI.


Troubleshooting#

command not found: pdfnative#

You installed via npx (one-shot) and not globally. Either prepend npx to every invocation, or run npm install --global pdfnative-cli.

JSON parse error: input too large#

The CLI caps input JSON at 50 MB to prevent memory exhaustion. For very large documents, either split the document into multiple PDFs or use the library directly with the streaming API.

Error: invalid private key#

Both RSA PKCS#1 and ECDSA P-256 keys are accepted, but they must be PEM-encoded. Convert DER to PEM with openssl pkcs8 -topk8 -in key.der -out key.pem -nocrypt. As of v0.3.0, ECDSA support in sign is fully wired (SEC1 / PKCS#8 P-256 via parseEcPrivateKey).

Encryption requires an owner password. Provide --owner-password <pass> or $PDFNATIVE_ENCRYPT_OWNER_PASS.#

Any encryption flag was set without an owner password. Provide it via --owner-password <pass> (legacy alias: --encrypt-owner-pass) or — recommended — the PDFNATIVE_ENCRYPT_OWNER_PASS env var so it never enters shell history.

Error: --tagged pdfa* and --encrypt-* are mutually exclusive#

ISO 19005-1 §6.3.2 forbids encryption in PDF/A. Pick one — either an archival PDF/A document, or an encrypted distribution copy, but not both.

E_NETWORK: opt-in network operation failed#

A TSA / OCSP / CRL fetch (sign --timestamp, ltv --online, doc-timestamp, verify --revocation online) failed at the transport level. The SSRF guard allows http/https only, blocks private / loopback / metadata addresses, follows no redirects, and enforces a 10 s timeout and a 5 MiB response cap — check the URL is public and reachable, or raise --timeout where the command exposes it (ltv, doc-timestamp). A malformed TSA response is E_PARSE instead. There is never a silent fallback: either the token is embedded and verified, or the command fails.

E_PARSE on a PDF that other tools open#

If the message mentions an inflate cap, one of the PDF's compressed streams expands past the anti-zip-bomb ceiling (default 100 MiB). For a trusted file, raise it with the global --max-inflate-size <bytes> (v1.4.0).

ENOENT: no such file or directory, 'D\' (Windows)#

This was a v0.1.0 / pre-v0.2.0 regression: --attachment D:\file.xml was split at the drive-letter colon. Fixed in v0.2.0 — make sure you're on pdfnative-cli@^0.2.0.

Layout file is ignored when I also pass CLI flags#

That is the intended precedence: CLI flags > --layout file > pdfnative defaults. To merge nested objects (e.g. a watermark in the layout file plus a --watermark-text on the CLI), the CLI now correctly merges params.layout with CLI-derived flags as of v0.2.0 (previously the JSON-embedded layout could be silently dropped — fixed).

--lang th does not produce Thai glyphs#

--lang only activates fonts that are registered in the CLI process — pass --font th as well so the bundled Noto Thai module is registered (--font covers all 22 bundled scripts, latin, emoji, color-emoji, math). A wrapper script that registers fonts and then spawns the CLI does not work: in-memory registration does not cross a process boundary. For a non-bundled font you ship yourself, call the library directly — see Recipes → Multilang fonts.

Signed PDF fails Adobe verification#

Ensure your certificate's signing-key usage extension includes digitalSignature (key usage 0). Self-signed certificates work for testing but require the validator to trust the issuer — pass --trust ca-root.pem to verify for self-signed setups.


Resources#


Citation#

If you use the CLI in research or academic pipelines, cite both repositories:

@software{pdfnative_cli_2026,
  title  = {pdfnative-cli: Official CLI for the pdfnative PDF generation library},
  author = {Nizoka},
  year   = {2026},
  url    = {https://github.com/Nizoka/pdfnative-cli},
  license = {MIT}
}