npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xenterprises/fastify-xpdf

v1.0.1

Published

Fastify plugin for PDF generation and manipulation. Convert HTML/Markdown to PDF, fill forms, and merge PDFs.

Readme

xPDF

Fastify v5 plugin for PDF generation and manipulation with a simple, intuitive library of methods.

Generate PDFs from HTML and Markdown, fill PDF forms, and merge PDFs. Works with any Fastify application and optionally integrates with xStorage for automatic cloud storage.

Requirements

  • Fastify v5.0.0+
  • Node.js v20+

Features

  • 📄 HTML to PDF - Convert HTML to PDF using Puppeteer
  • 📝 Markdown to PDF - Convert Markdown to PDF with styled formatting
  • 📋 Form Filling - Fill PDF form fields with values
  • ✏️ Form Flattening - Make form fields non-editable
  • 🔗 PDF Merging - Combine multiple PDFs into one
  • 💾 Optional Storage - Save PDFs to S3-compatible storage via xStorage
  • 🔌 Simple API - Methods decorated on Fastify instance
  • 🎯 Library Pattern - Use as a service, not as HTTP routes

Installation

npm install @xenterprises/fastify-xpdf puppeteer marked pdf-lib fastify@5

For optional storage integration:

npm install @xenterprises/fastify-xstorage

Quick Start

import Fastify from "fastify";
import xPDF from "@xenterprises/fastify-xpdf";

const fastify = Fastify({ logger: true });

// Register xPDF
await fastify.register(xPDF, {
  headless: true,
  useStorage: false,
  defaultFolder: "pdfs",
});

// Generate PDF from HTML
const result = await fastify.xPDF.generateFromHtml("<h1>Hello World</h1>", {
  filename: "hello.pdf",
});

console.log(result);
// {
//   buffer: Buffer,
//   filename: "hello.pdf",
//   size: 12345,
//   storageKey?: "pdfs/hello.pdf",      // Only if saveToStorage: true
//   url?: "https://..."                 // Only if saveToStorage: true
// }

await fastify.listen({ port: 3000 });

Configuration

Plugin Options

await fastify.register(xPDF, {
  // Puppeteer options
  headless: true,                          // Default: true
  args: ["--no-sandbox"],                  // Chrome launch args

  // xStorage integration (optional)
  useStorage: false,                       // Default: false
  defaultFolder: "pdfs",                   // Default storage folder

  // PDF generation defaults
  format: "A4",                            // Default page format
  printBackground: true,                   // Include background in PDFs
  margin: {                                // Default margins
    top: "1cm",
    right: "1cm",
    bottom: "1cm",
    left: "1cm",
  },
});

Core API

All methods are available on the fastify.xPDF namespace.

fastify.xPDF.generateFromHtml(html, options)

Convert HTML to PDF.

const result = await fastify.xPDF.generateFromHtml("<h1>Invoice</h1>", {
  filename: "invoice.pdf",
  format: "A4",
  landscape: false,
  margin: { top: "1cm", right: "1cm", bottom: "1cm", left: "1cm" },
  printBackground: true,
  saveToStorage: false,
  folder: "pdfs",
});

console.log(result);
// {
//   buffer: Buffer,              // PDF content as Buffer
//   filename: "invoice.pdf",     // Output filename
//   size: 245678,                // File size in bytes
//   storageKey?: "pdfs/...",     // Only if saveToStorage: true
//   url?: "https://..."          // Only if saveToStorage: true
// }

Options:

  • filename - Output filename (default: generated-{timestamp}.pdf)
  • format - Page format: A4, Letter, Legal, A3, A5, etc. (default: A4)
  • landscape - Landscape orientation (default: false)
  • margin - Page margins (default: 1cm all sides)
  • printBackground - Include background graphics (default: true)
  • saveToStorage - Save to xStorage (default: false)
  • folder - Storage folder (default: 'pdfs')

fastify.xPDF.generateFromMarkdown(markdown, options)

Convert Markdown to PDF.

const markdown = `
# Invoice

**Date:** January 1, 2024

## Items

1. Item 1: $100
2. Item 2: $200

**Total:** $300
`;

const result = await fastify.xPDF.generateFromMarkdown(markdown, {
  filename: "invoice.pdf",
  saveToStorage: true,
  folder: "invoices",
});

console.log(result);
// {
//   buffer: Buffer,
//   filename: "invoice.pdf",
//   size: 234567,
//   storageKey: "invoices/invoice-1234567890.pdf",
//   url: "https://storage.example.com/invoices/invoice-1234567890.pdf"
// }

Supports:

  • Headings (H1-H6)
  • Paragraphs and line breaks
  • Lists (ordered and unordered)
  • Code blocks with syntax highlighting
  • Tables
  • Blockquotes
  • Links
  • Emphasis (bold, italic)

fastify.xPDF.fillForm(pdfBuffer, fieldValues, options)

Fill PDF form fields and optionally flatten.

// Download PDF from storage
const pdfBuffer = await fastify.xStorage.download("forms/application.pdf");

// Fill form
const result = await fastify.xPDF.fillForm(
  pdfBuffer,
  {
    firstName: "John",
    lastName: "Doe",
    email: "[email protected]",
    agreeToTerms: true,
  },
  {
    flatten: true,  // Make fields non-editable
    filename: "application-filled.pdf",
    saveToStorage: true,
    folder: "submitted-forms",
  }
);

console.log(result);
// {
//   buffer: Buffer,
//   filename: "application-filled.pdf",
//   size: 256789,
//   storageKey: "submitted-forms/...",
//   url: "https://..."
// }

Options:

  • flatten - Make form fields non-editable (default: true)
  • filename - Output filename
  • saveToStorage - Save to xStorage (default: false)
  • folder - Storage folder (default: 'pdfs')

Supported Field Types:

  • Text fields: field.setText(value)
  • Checkboxes: field.check() or field.uncheck()
  • Radio buttons: field.select(value)
  • Dropdowns: field.select(value)

fastify.xPDF.listFormFields(pdfBuffer)

List all form fields in a PDF.

const pdfBuffer = await fastify.xStorage.download("forms/application.pdf");

const fields = await fastify.xPDF.listFormFields(pdfBuffer);

console.log(fields);
// [
//   { name: "firstName", type: "text", value: null },
//   { name: "lastName", type: "text", value: null },
//   { name: "email", type: "text", value: null },
//   { name: "agreeToTerms", type: "checkbox", value: false }
// ]

Returns:

  • name - Field name
  • type - Field type (text, checkbox, radio, dropdown, option)
  • value - Current field value

fastify.xPDF.mergePDFs(pdfBuffers, options)

Merge multiple PDFs into one.

// Download PDFs from storage
const page1 = await fastify.xStorage.download("documents/page1.pdf");
const page2 = await fastify.xStorage.download("documents/page2.pdf");
const page3 = await fastify.xStorage.download("documents/page3.pdf");

const result = await fastify.xPDF.mergePDFs([page1, page2, page3], {
  filename: "complete-document.pdf",
  saveToStorage: true,
  folder: "merged",
});

console.log(result);
// {
//   buffer: Buffer,
//   filename: "complete-document.pdf",
//   size: 512345,
//   pageCount: 15,  // Total pages from all 3 PDFs
//   storageKey: "merged/complete-document-1234567890.pdf",
//   url: "https://..."
// }

Options:

  • filename - Output filename (default: merged-{timestamp}.pdf)
  • saveToStorage - Save to xStorage (default: false)
  • folder - Storage folder (default: 'pdfs')

Returns:

  • buffer - Merged PDF buffer
  • filename - Output filename
  • size - File size in bytes
  • pageCount - Total number of pages
  • storageKey - Storage key (if saved)
  • url - Public URL (if saved)

xStorage Integration

xPDF works seamlessly with xStorage for automatic cloud storage of generated PDFs.

Basic Integration

import Fastify from "fastify";
import xStorage from "@xenterprises/fastify-xstorage";
import xPDF from "@xenterprises/fastify-xpdf";

const fastify = Fastify();

// Register xStorage first
await fastify.register(xStorage, {
  endpoint: "https://nyc3.digitaloceanspaces.com",
  region: "nyc3",
  accessKeyId: process.env.DO_SPACES_KEY,
  secretAccessKey: process.env.DO_SPACES_SECRET,
  bucket: "my-bucket",
  publicUrl: "https://my-bucket.nyc3.digitaloceanspaces.com",
});

// Register xPDF (will auto-detect xStorage)
await fastify.register(xPDF, {
  useStorage: true,
  defaultFolder: "pdfs",
});

// Now all PDFs can be saved to storage
const result = await fastify.xPDF.generateFromHtml("<h1>Report</h1>", {
  saveToStorage: true,
  folder: "reports",
});

console.log(result.url); // https://my-bucket.nyc3.digitaloceanspaces.com/reports/...

Advanced: Retrieve and Process Stored PDFs

// Get PDF from storage
const storedUrl = "https://my-bucket.nyc3.digitaloceanspaces.com/forms/template.pdf";
const key = storedUrl.replace(process.env.STORAGE_PUBLIC_URL + "/", "");
const pdfBuffer = await fastify.xStorage.download(key);

// Fill form and save result
const result = await fastify.xPDF.fillForm(pdfBuffer, { name: "John" }, {
  saveToStorage: true,
  folder: "submitted",
});

console.log(result.url); // Direct link to filled form

Helper Utilities

import { helpers } from "@xenterprises/fastify-xpdf";

// Generate unique PDF filename
helpers.generatePdfFilename("invoice");
// "invoice-1704067200000.pdf"

// Validate PDF buffer
helpers.isValidPdfBuffer(buffer);
// true/false

// Get PDF metadata
helpers.getPdfMetadata(buffer);
// { size: 12345 }

// Sanitize filename
helpers.sanitizeFilename("My File (2024).pdf");
// "my_file_2024.pdf"

// Get page format dimensions
helpers.getPageFormat("A4");
// { width: 8.27, height: 11.7 }

Usage Examples

Document Generation

fastify.post("/invoices", async (request, reply) => {
  const { items, total, date } = request.body;

  const html = `
    <h1>Invoice</h1>
    <p>Date: ${date}</p>
    <ul>
      ${items.map((item) => `<li>${item.name}: $${item.price}</li>`).join("")}
    </ul>
    <h2>Total: $${total}</h2>
  `;

  const result = await fastify.xPDF.generateFromHtml(html, {
    filename: "invoice.pdf",
    saveToStorage: true,
    folder: "invoices",
  });

  return { success: true, url: result.url };
});

Form Processing

fastify.post("/applications/:id/submit", async (request, reply) => {
  const { id } = request.params;
  const formData = request.body;

  // Get form template
  const template = await fastify.xStorage.download("forms/application-template.pdf");

  // Fill with submitted data
  const result = await fastify.xPDF.fillForm(template, formData, {
    flatten: true,
    filename: `application-${id}.pdf`,
    saveToStorage: true,
    folder: "applications",
  });

  // Save record in database
  await fastify.db.application.create({
    id,
    pdfKey: result.storageKey,
    pdfUrl: result.url,
    submittedAt: new Date(),
  });

  return { success: true, pdfUrl: result.url };
});

Report Compilation

fastify.post("/reports/compile", async (request, reply) => {
  const { reportIds } = request.body;

  // Get all report PDFs
  const pdfBuffers = await Promise.all(
    reportIds.map((id) =>
      fastify.xStorage.download(`reports/${id}.pdf`)
    )
  );

  // Merge into single document
  const result = await fastify.xPDF.mergePDFs(pdfBuffers, {
    filename: "compiled-report.pdf",
    saveToStorage: true,
    folder: "compiled",
  });

  return {
    success: true,
    filename: result.filename,
    pages: result.pageCount,
    url: result.url,
  };
});

Best Practices

  1. Use xStorage for Production - Automatically manage PDF storage and access
  2. Validate Input - Sanitize HTML/Markdown to prevent XSS in PDF generation
  3. Handle Large PDFs - Set reasonable timeouts for Puppeteer operations
  4. Organize with Folders - Use meaningful folder structures (e.g., "invoices/2024")
  5. Store Metadata - Keep records of storage keys in your database
  6. Error Handling - Always wrap PDF operations in try-catch blocks
  7. Memory Management - PDFs are loaded in memory; handle size carefully
  8. Reuse Browser - Plugin maintains single browser instance for efficiency

Configuration Profiles

Development

await fastify.register(xPDF, {
  headless: true,
  useStorage: false,  // Save to disk instead
  format: "A4",
});

Production with Storage

await fastify.register(xPDF, {
  headless: true,
  useStorage: true,
  defaultFolder: "pdfs",
  args: ["--no-sandbox", "--disable-setuid-sandbox"],
});

High Performance

await fastify.register(xPDF, {
  headless: true,
  args: [
    "--no-sandbox",
    "--disable-setuid-sandbox",
    "--disable-gpu",
    "--disable-dev-shm-usage",
  ],
  useStorage: true,
});

Troubleshooting

Puppeteer Launch Fails

Error: Timeout waiting for browser to start

Solution: Increase available memory and ensure Chrome dependencies are installed

# macOS
brew install chromium

# Linux
apt-get install -y chromium-browser

# Or use args to help with sandboxing
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"]

Form Fields Not Filling

Error: Form field not found: fieldName

Solution: List form fields first to verify correct names

const fields = await fastify.xPDF.listFormFields(pdfBuffer);
console.log(fields); // Check exact field names

PDF Generation Times Out

Error: Timeout waiting for page to load

Solution: Simplify HTML, remove external resources, or optimize content

// ❌ Avoid external resources
const html = '<img src="https://example.com/image.jpg">';

// ✅ Use inline or relative paths
const html = '<img src="/images/logo.png">';

Storage Integration Not Working

Error: xStorage plugin not registered

Solution: Register xStorage before xPDF

// Register xStorage FIRST
await fastify.register(xStorage, {...});

// Then register xPDF
await fastify.register(xPDF, {
  useStorage: true,
});

Performance Tips

  • Use headless: true (already default)
  • Enable printBackground: false for simpler documents
  • Avoid large images or external resources in HTML
  • Use Markdown for simple, styled documents
  • Batch operations when merging many PDFs

Testing

npm test

See TESTING.md for comprehensive testing guide.

Examples

See EXAMPLES.md for complete real-world examples.

License

ISC