---
title: "Printer-friendly version - PKI Express"
description: "How to make a printer-friendly version of a signed file with PKI Express: PdfMarker and PdfHelper stamp the signers and a verification code on every page and add a manifest page with a QR code."
canonical: "https://docs.lacunasoftware.com/en-us/articles/pki-express/printer-friendly-version"
locale: "en"
product: "PKI Express"
last_updated: "2026-10-13"
---

# Printer-friendly version - PKI Express

A printed copy of a signed PDF loses its digital signatures. A printer-friendly version tells the reader who signed the document and how to
get the signed file back: [PKI Express](https://docs.lacunasoftware.com/en-us/articles/pki-express) validates the signatures, marks every page with the signers' names and a verification code,
and adds a manifest page with a QR code. Your site then looks the document up by that code. 
PKI Express added PDF editing (the `edit-pdf` command) in 1.3.0, "enabling generation of printer-friendly versions". 

The code is Node.js, from the [PKI Suite samples](https://github.com/LacunaSoftware/PkiSuiteSamples), with `pki-express` (1.16.2); the
Python sample does the same. 

## Flow

1. Generate a verification code for the signed PDF and store it with the file.
1. Open and validate the PDF's signatures with `PadesSignatureExplorer`.
1. Build the marks with `PdfHelper`: the signers on every page, and a manifest on a new page.
1. Call `PdfMarker.apply()`, which runs PKI Express's `edit-pdf` command and writes the marked PDF.
1. When someone types the code on your site, look the file up and show or download it.

## 1. The verification code

The sample generates a 16-character code from cryptographically strong random bytes and stores the code and the file id in the web session,
both ways. Store them in your database instead. 

```js
let verificationCode = StorageMock.getVerificationCode(req.session, fileId);
if (!verificationCode) {
	verificationCode = Util.generateVerificationCode();
	StorageMock.setVerificationCode(req.session, fileId, verificationCode);
}
```

The code protects access to your documents: the Java library has an `AlphaCode` class to generate it, described in
[Generating alphanumeric codes](https://docs.lacunasoftware.com/en-us/articles/pki-express/java/alpha-codes). 

## 2. Open the signatures

```js
const sigExplorer = new PadesSignatureExplorer();
Util.setPkiDefaults(sigExplorer);
sigExplorer.validate = true;
sigExplorer.setSignatureFileFromPathSync(pdfPath);
sigExplorer.open().then((signature) => { /* build the marks from signature.signers */ });
```

Each signer has its `certificate`, `signingTime` and `validationResults`; see [Validate signatures](https://docs.lacunasoftware.com/en-us/articles/pki-express/validate-signatures#results). 

## 3. Mark every page

`PdfHelper` is a fluent API that builds marks, containers and elements (text, image, QR code); `PdfMarker` holds the list of marks. The
sample puts the ICP-Brasil logo in the bottom-right corner and the message along the bottom and the right edge of every page: 

```js
const pdfMarker = new PdfMarker();
Util.setPkiDefaults(pdfMarker);
pdfMarker.setFileFromPathSync(pdfPath);
const pdf = new PdfHelper();
pdfMarker.marks.push(
	pdf.mark()
		.onAllPages()
		.onContainer(pdf.container().width(1).anchorRight(1).height(1)
			.anchorBottom(1))
		.addElement(
			pdf.imageElement()
				.withOpacity(75)
				.withImageContent(Util.getIcpBrasilLogoContent(), 'image/png'),
		),
);
pdfMarker.marks.push(
	pdf.mark()
		.onAllPages()
		.onContainer(pdf.container().height(2).anchorBottom().varWidth()
			.margins(1.5, 3.5))
		.addElement(pdf.textElement().withOpacity(75).addSectionFromText(allPagesMessage)),
);
```

* `allPagesMessage` names the signers and gives the verification site and the code. 
* Sizes and positions are in centimeters by default (`measurementUnits`). 
* A text element can be rotated with `rotate90CounterClockwise()`, as the sample does for the right edge. 

## 4. Add the manifest page

A mark with `onNewPage()` adds a page. The sample fills it from the top down: the logo, a QR code with the verification link, the title, the
code, one line per signer with a valid or invalid icon, and the verification link. 

```js
const manifestMark = pdf.mark()
	.onNewPage()
	.onContainer(pdf.container().varWidthAndHeight().margins(2.54, 2.54));
manifestMark.addElement(
	pdf.qrCodeElement()
		.onContainer(pdf.container().height(elementHeight).anchorTop(verticalOffset)
			.width(elementHeight)
			.anchorRight())
		.withQRCodeData(verificationLink),
);
// ... more elements
pdfMarker.marks.push(manifestMark);
outputFile = `${uuidv4()}.pdf`;
pdfMarker.outputFile = path.join(APP_ROOT, 'app-data', outputFile);
return pdfMarker.apply();
```

* `apply()` writes the marked PDF to `outputFile`, or over the original with `overwriteOriginalFile`. 
* TODO(docs): whether the marked copy keeps the original signatures (PKI SDK's `PdfMarker` removes them; see the [PKI SDK page](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/printer-friendly-version)).

## 5. Check a code

The verification page parses the code, finds the file and opens its signatures again to show them: 

```js
const verificationCode = Util.parseVerificationCode(req.query.code);
const fileId = StorageMock.lookupVerificationCode(req.session, verificationCode);
const sigExplorer = new PadesSignatureExplorer();
Util.setPkiDefaults(sigExplorer);
sigExplorer.setSignatureFileFromPathSync(StorageMock.getDataPath(fileId));
sigExplorer.validate = true;
sigExplorer.open().then((signature) => { /* render the signers and a link to the file */ });
```

## For a CAdES file

For a `.p7s`, the Node.js sample opens it with `CadesSignatureExplorer` and marks an empty PDF that ships with the sample, so the result is a
manifest that lists the signers, with the verification code and a QR code. 

## In each language

| Library | `PdfMarker` | `PdfHelper` (fluent API) |
|---|---|---|
| Node.js | yes; `setFileFromPath`, `outputFile`, `marks`, `apply()` | yes |
| Java | yes | yes |
| PHP | yes; `setFile`, `setOutputFile`, `apply()` | no: build `PdfMark` objects |
| Python | yes; `set_file_from_path`, `output_file`, `marks`, `apply()` | yes |
| Ruby | no | no |

## See also

* [Validate signatures](https://docs.lacunasoftware.com/en-us/articles/pki-express/validate-signatures)
* [Sign a PDF (PAdES)](https://docs.lacunasoftware.com/en-us/articles/pki-express/pades-signature)
* [Printer-friendly version with PKI SDK](https://docs.lacunasoftware.com/en-us/articles/pki-sdk/printer-friendly-version)
* [PKI Express quick reference](https://docs.lacunasoftware.com/en-us/articles/pki-express/quick-reference)

**Demos and samples**

- Live demo: [Make a printer-friendly version](https://try.lacunasoftware.com/en/pki-suite/printer-friendly-version)
- Make a printer-friendly version of a signed PDF using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/printer-version-pades-express.js) · [Python](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/python/flask/sample/views/printer_version_pades_express.py)
- Make a printer-friendly version of a CMS/CAdES signature using PKI Express: [Node.js](https://github.com/LacunaSoftware/PkiSuiteSamples/blob/master/nodejs/expressmvc/routes/printer-version-cades-express.js)
