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.3.0

Published

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

Readme

@xenterprises/fastify-xpdf

Fastify 5 plugin for PDF generation and manipulation — convert HTML/Markdown/URLs to PDF with Puppeteer, fill PDF forms, merge PDFs, and extract pages with pdf-lib, with optional S3-compatible storage via xStorage. For services that need to produce invoices, reports, contracts, and filled forms from a single fastify.xPdf namespace.

Install

npm install @xenterprises/fastify-xpdf fastify@5

Peer dependencies: fastify@^5 (required), @xenterprises/fastify-xstorage (optional — only needed when calling methods with saveToStorage: true).

Installing downloads a Chrome build via Puppeteer. Contributors who only run the test suite can skip it: PUPPETEER_SKIP_DOWNLOAD=1 npm install — the tests never launch Chrome.

Minimal example

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

const fastify = Fastify();

await fastify.register(xPDF);

const result = await fastify.xPdf.generateFromHtml("<h1>Hello World</h1>");
// result: { buffer, filename, size }

All options have defaults; everything is configured through the register options object — the plugin never reads process.env.

Options

| Name | Type | Default | Required | Description | |------|------|---------|----------|-------------| | headless | boolean | true | No | Run the Puppeteer browser in headless mode | | args | string[] | ["--no-sandbox", "--disable-setuid-sandbox"] | No | Chrome/Chromium launch arguments | | browserFactory | function | puppeteer.launch | No | Injectable browser factory (launchOptions) => Promise<browser>. Receives { headless, args }; used by tests to substitute a fake browser without launching Chrome | | defaultFolder | string | "pdfs" | No | Default storage folder for saved PDFs | | format | string | "A4" | No | Default page format (A4, Letter, A3, A5, Tabloid, Ledger, Legal, A0–A6) | | printBackground | boolean | true | No | Print background graphics and colors by default | | margin | object | { top: "1cm", right: "1cm", bottom: "1cm", left: "1cm" } | No | Default page margins (CSS units) |

Invalid option types fail fast at registration — see Error behavior.

Decorators

fastify.xPdf

All methods are available on the fastify.xPdf decorator.

generateFromHtml(html, options?)

Generate a PDF from HTML content.

| Param | Type | Description | |-------|------|-------------| | html | string | HTML content (required, non-empty) | | options.filename | string | Output filename (auto-generated if omitted) | | options.format | string | Page format override | | options.landscape | boolean | Landscape orientation | | options.margin | object | Page margins override | | options.printBackground | boolean | Print background override | | options.displayHeaderFooter | boolean | Enable header/footer | | options.headerTemplate | string | Header HTML template | | options.footerTemplate | string | Footer HTML template | | options.saveToStorage | boolean | Save to xStorage | | options.folder | string | Storage folder override |

Returns: { buffer, filename, size, storageKey?, url? }

generateFromMarkdown(markdown, options?)

Convert Markdown to PDF. Accepts the same options as generateFromHtml. The Markdown is parsed with marked and wrapped in a styled HTML template.

generateFromUrl(url, options?)

Navigate to a URL and generate a PDF of the rendered page.

| Param | Type | Description | |-------|------|-------------| | url | string | URL to render (required, must be valid) | | options.waitFor | string | Puppeteer waitUntil value (default: "networkidle2") | | options.timeout | number | Navigation timeout in ms (default: 30000) |

Plus all options from generateFromHtml.

fillForm(pdfBuffer, fieldValues, options?)

Fill PDF form fields (text, checkbox, radio, dropdown).

| Param | Type | Description | |-------|------|-------------| | pdfBuffer | Buffer | Source PDF with form fields | | fieldValues | object | { fieldName: value } key-value pairs | | options.flatten | boolean | Flatten form after filling (default: true) | | options.filename | string | Output filename | | options.saveToStorage | boolean | Save to xStorage | | options.folder | string | Storage folder |

Returns: { buffer, filename, size, storageKey?, url? }

listFormFields(pdfBuffer)

List all form fields in a PDF.

Returns: [{ name, type, value }] — type is one of: text, checkbox, radio, dropdown, option, button, signature, unknown.

mergePDFs(pdfBuffers, options?)

Merge multiple PDFs into a single document.

| Param | Type | Description | |-------|------|-------------| | pdfBuffers | Buffer[] | Array of PDF buffers (required, non-empty) | | options.filename | string | Output filename | | options.saveToStorage | boolean | Save to xStorage | | options.folder | string | Storage folder |

Returns: { buffer, filename, size, pageCount, storageKey?, url? }

extractPages(pdfBuffer, pageIndices, options?)

Extract specific pages from a PDF into a new document.

| Param | Type | Description | |-------|------|-------------| | pdfBuffer | Buffer | Source PDF buffer | | pageIndices | number[] | Zero-based page indices to extract | | options.filename | string | Output filename | | options.saveToStorage | boolean | Save to xStorage | | options.folder | string | Storage folder |

Returns: { buffer, filename, size, pageCount, storageKey?, url? }

getPageCount(pdfBuffer)

Get the number of pages in a PDF. Returns a number.

getMetadata(pdfBuffer)

Get PDF metadata.

Returns: { pageCount, title, author, subject, creator, creationDate, modificationDate, size }

Exported helpers

Available via import { helpers } from "@xenterprises/fastify-xpdf/helpers":

| Helper | Description | |--------|-------------| | generatePdfFilename(baseName?) | Generate unique filename with timestamp | | isValidPdfBuffer(buffer) | Check if buffer starts with %PDF header | | getPdfMetadata(buffer) | Get { size } from a PDF buffer | | formatPdfOptions(options, defaults) | Merge options with defaults (deep-merges margin) | | sanitizeFilename(filename) | Remove unsafe characters, lowercase | | wrapHtmlTemplate(content) | Wrap HTML fragment in a full styled document | | parseMargin(margin) | Convert string or object margin to Puppeteer format | | getPageFormat(format?) | Get { width, height } in inches for a format name | | saveToStorage(fastify, buffer, filename, folder) | Upload PDF to xStorage (returns null when xStorage is not registered; upload errors propagate) |

Error behavior

Registration fails fast on invalid option types. Messages name the plugin, the option, and an example:

xpdf: option `headless` must be a boolean, e.g. `app.register(xPDF, { headless: true })`
xpdf: option `args` must be an array of strings, e.g. `app.register(xPDF, { args: ["--no-sandbox"] })`
xpdf: option `defaultFolder` must be a string, e.g. `app.register(xPDF, { defaultFolder: "pdfs" })`
xpdf: option `format` must be a string, e.g. `app.register(xPDF, { format: "A4" })`
xpdf: option `printBackground` must be a boolean, e.g. `app.register(xPDF, { printBackground: false })`
xpdf: option `margin` must be an object with top/right/bottom/left strings, e.g. `app.register(xPDF, { margin: { top: "1cm", ... } })`
xpdf: option `browserFactory` must be a function returning a Puppeteer-compatible browser, e.g. `app.register(xPDF, { browserFactory: async () => puppeteer.launch() })`

Method calls throw real Error objects prefixed with [xPDF]:

| Error | When | |-------|------| | [xPDF] HTML content must be a non-empty string | Empty/null HTML passed to generateFromHtml | | [xPDF] Markdown content must be a non-empty string | Empty/null markdown passed to generateFromMarkdown | | [xPDF] URL must be a non-empty string | Empty/null URL passed to generateFromUrl | | [xPDF] URL must be a valid URL | Invalid URL format | | [xPDF] Invalid PDF buffer | Non-PDF buffer passed to form/merge/extract methods | | [xPDF] fieldValues must be an object | Non-object passed as fieldValues to fillForm | | [xPDF] pdfBuffers must be a non-empty array... | Empty/null array passed to mergePDFs | | [xPDF] One or more invalid PDF buffers provided | Invalid buffer in merge array | | [xPDF] pageIndices must be a non-empty array... | Invalid pageIndices in extractPages | | [xPDF] Each page index must be a non-negative integer | Non-integer or negative page index | | [xPDF] Page index N out of range... | Page index exceeds PDF page count | | [xPDF] Failed to initialize PDF browser | Puppeteer browser launch failure | | [xPDF] Failed to process PDF during merge | Corrupt PDF during merge operation | | xpdf: saveToStorage requires the xStorage plugin — register @xenterprises/fastify-xstorage first or pass saveToStorage: false | saveToStorage: true passed but @xenterprises/fastify-xstorage is not registered |

Storage upload failures (when xStorage is registered) propagate the original xStorage error to the caller; they are logged message-only and never swallowed.

How it works

The plugin uses two engines:

  1. Puppeteer (Chrome headless) for HTML/Markdown/URL to PDF generation. A single browser instance is lazily created on the first generation call via the (injectable) browserFactory and reused across requests. It auto-reconnects if the browser disconnects. The browser is closed on Fastify shutdown via the onClose hook. Pages are opened per call and always closed in a finally block.

  2. pdf-lib for all PDF manipulation (form filling, merging, page extraction, metadata). These operations are pure JavaScript with no browser dependency.

When @xenterprises/fastify-xstorage is registered, any method result can be uploaded to S3-compatible storage by passing saveToStorage: true on that call (with an optional folder override). Passing saveToStorage: true without xStorage registered throws an actionable error — saving is gated per call, there is no plugin-level storage toggle.

The plugin registers itself as xpdf via fastify-plugin with a fastify: "5.x" constraint, so it can be registered in any scope.

Testing

The test suite is fully offline: it never launches or downloads Chrome and needs no env vars. The Puppeteer path is tested against a fake browser injected through the browserFactory option; the pdf-lib paths run against real PDF buffers generated in-test. To install dependencies without downloading Chrome: PUPPETEER_SKIP_DOWNLOAD=1 npm install.

npm test        # node --test
npm run lint    # biome check
npm run format  # biome format --write

Requirements

  • Node.js >= 20
  • Fastify ^5

License

Proprietary — All Rights Reserved X Enterprises. See LICENSE.