Documentation

Encryption

Protect a PDF with a password and jasy encrypts it with AES-256 (V5/R6, the newest handler in the PDF spec). It runs on the platform's WebCrypto, so there are no native dependencies and no Java - the same code encrypts in Node and right in the browser.

Set a password

Pass an encrypt option to renderToBytes (or renderPdf). A userPassword is all you need - any viewer asks for it before it opens the document.

import { Document, Page, Text, renderToBytes } from "@jasy/pdf";

const doc = Document([Page([Text("Confidential")])]);

const bytes = await renderToBytes(doc, {
  encrypt: { userPassword: "secret" },
});

Owner password and permissions

Add an ownerPassword for full rights, and permissions to allow or deny actions. The owner password defaults to the user password when you leave it out, and every permission defaults to allowed.

const bytes = await renderToBytes(doc, {
  encrypt: {
    userPassword: "secret", // to open the document
    ownerPassword: "owner", // full rights
    permissions: {
      printing: false, // disallow printing
      copying: false, // disallow text/content extraction
      modifying: true,
      annotating: true,
    },
  },
});

Opening one

The other half: jasy reads encrypted PDFs too, which is what makes filling a protected form possible. Pass the password and everything downstream stays ordinary:

import { PdfDocument, fillForm } from "@jasy/pdf/edit";

const doc = await PdfDocument.open(bytes, { password: "secret" });
const { bytes: filled } = await fillForm(bytes, { name: "Ada" }, { password: "secret" });

Reading is broad, writing is narrow. jasy opens AES-256 (R6, R5), AES-128 (R4) and RC4 (R2/R3), because those files exist and you may have to open one. It writes only AES-256 R6: handing someone protection that is known to be broken is not a feature, so the legacy handler refuses to encrypt.

Worth knowing when you compare: PDFKit and react-pdf both default to 40-bit RC4 when you ask for a password, and neither can emit R6 at all. Ghostscript 10 refuses anything past R3.

The errors are named, never generic: no password, wrong password, or a revision jasy does not implement - three different things a caller can act on differently.

Good to know

  • AES-256, R6. The current ISO 32000-2 handler - not the older revisions some libraries still ship.
  • Pure JS, isomorphic. WebCrypto under the hood: no native crypto, zero extra dependencies, identical in Node and the browser.
  • Vue and Nuxt too. In @jasy/vue pass it as the third argument - renderToPdf(Doc, props, { encrypt: { userPassword: "secret" } }). In @jasy/nuxt use renderOptions.
  • Not for PDF/A. PDF/A (and therefore ZUGFeRD / Factur-X invoices) forbids encryption, so encrypting one is rejected with a clear error.
jasypdf

Declarative PDFs in pure TypeScript. ZUGFeRD & XRechnung compliant, with no headless browser and no Java.

Resources

© 2026 Florian Heuberger · MIT License

Built with Nuxt · self-hosted fonts · no trackers