Ask Anvil

Answers to questions about automating PDFs, e-signatures, Webforms, and other paperwork problems.
E-signatures
Categories

How do I append a certificate of completion page to a signed PDF in Node.js?

This function loads the signed PDF, hashes the original bytes with Node's built-in crypto module, and appends a one-page certificate with pdf-lib. It was run end to end with pdf-lib 1.17.1 on Node 22.

const { PDFDocument, StandardFonts, rgb } = require('pdf-lib');
const { createHash } = require('node:crypto');
const fs = require('node:fs');

async function addCertificatePage(signedPdfBytes, signers) {
  const sha256 = createHash('sha256').update(signedPdfBytes).digest('hex');
  const pdf = await PDFDocument.load(signedPdfBytes);
  const font = await pdf.embedFont(StandardFonts.Helvetica);
  const bold = await pdf.embedFont(StandardFonts.HelveticaBold);

  const page = pdf.addPage([612, 792]);
  let y = 740;
  page.drawText('Certificate of completion', { x: 54, y, size: 20, font: bold });
  y -= 32;
  page.drawText('Original document SHA-256:', { x: 54, y, size: 10, font: bold });
  y -= 14;
  page.drawText(sha256, { x: 54, y, size: 8, font, color: rgb(0.2, 0.2, 0.2) });
  y -= 32;

  for (const s of signers) {
    page.drawText(s.name + ' <' + s.email + '>', { x: 54, y, size: 11, font: bold });
    y -= 15;
    page.drawText('Signed: ' + s.signedAt + ' (UTC)   IP: ' + s.ip, { x: 54, y, size: 10, font });
    y -= 26;
  }
  return pdf.save();
}
const signers = [{ name: 'Ada Lovelace', email: 'ada@example.com', signedAt: '2026-10-12T15:04:05.000Z', ip: '203.0.113.7' }];
const original = fs.readFileSync('signed.pdf');
const withCert = await addCertificatePage(original, signers);
fs.writeFileSync('signed-with-certificate.pdf', withCert);

How it works

The SHA-256 is calculated from the file as it was before the certificate page exists. A page cannot contain a hash of itself, so the hash identifies the original document, and anyone can recompute it from the stored original and compare. Store the original bytes next to the output file.

Pass signing times as UTC ISO strings. Formatting them for display later is easy, but recovering the original instant from a local-time string is not.

Caveats

Do not re-save a PDF that already carries a certificate-based digital signature. Changing the file after signing can invalidate that signature, so keep the certificate as a separate PDF in that case, or add it before the signing step.

The standard Helvetica font only encodes WinAnsi characters. Names like "Zoë Müller" work, but a name such as "李雷" throws "WinAnsi cannot encode". For international signers, embed a Unicode TrueType font with the @pdf-lib/fontkit package instead of using StandardFonts.

The code does not wrap long lines. A long email address or many signers will run off the page, so add line wrapping and extra pages before using this in production.

Back to All Questions

The fastest way to build software for documents

Anvil Document SDK is a comprehensive toolbox for product teams launching document flows where PDF filling, signing, and complex conditional scenarios are necessary.
Explore Anvil
Anvil Webforms
Marketing Mode