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

safeer-pdf-generator

v1.3.2

Published

Framework-agnostic PDF generation library with chunking, merging, S3 upload, and email delivery

Readme

safeer-pdf-generator

npm version License: MIT Downloads

A powerful, framework-agnostic TypeScript library for generating PDF reports with chunking, merging, S3 upload, email delivery, lifecycle events, and webhook dispatch.

✨ Features

  • 🚀 High Performance — Concurrent chunk processing with memory optimization
  • 📊 3 Built-in Templatesdefault, simple, and modern with full customization
  • 🎨 Custom Templates — Inline functions, CSS overrides, or register your own
  • 🔧 Framework Agnostic — Works with Express, NestJS, Fastify, or standalone
  • ☁️ S3 Integration — Direct upload to Amazon S3
  • 📧 Email Delivery — Send PDFs as attachments or download links
  • 🔀 PDF Operations — Merge, split, and manipulate existing PDFs
  • 🌐 BYOB Browser — Bring Your Own Browser for connection pooling
  • 📡 Lifecycle Events — Typed event emitters for every stage
  • 🔔 Webhook Dispatcher — Auto-POST to your backend with HMAC signing
  • 💾 Memory Efficient — Handle large datasets (100k+ records)
  • 🛡️ TypeScript — Full type safety and IntelliSense support

📦 Installation

Prerequisites

This package requires Chromium for PDF generation:

# Install Chromium
sudo apt-get install chromium-browser

# Or set custom path
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

Package Installation

npm install safeer-pdf-generator

# Optional peer dependencies
npm install @aws-sdk/client-s3 nodemailer

🚀 Quick Start

import { generatePdf } from 'safeer-pdf-generator';

const result = await generatePdf({
  title: 'Sales Report',
  data: [
    { name: 'John Doe', sales: 1200, region: 'North' },
    { name: 'Jane Smith', sales: 1500, region: 'South' },
  ],
  columns: [
    { key: 'name', title: 'Name', dataIndex: 'name' },
    { key: 'sales', title: 'Sales', dataIndex: 'sales' },
    { key: 'region', title: 'Region', dataIndex: 'region' },
  ],
});

console.log(`PDF: ${result.fileName} (${result.pageCount} pages, ${result.durationMs}ms)`);

🎨 Custom Templates

Built-in Templates

Three templates are available out of the box:

// Modern template (gradient header, card-style info)
await generatePdf({ ...options, template: 'modern' });

// Default template (classic table with company header)
await generatePdf({ ...options, template: 'default' });

// Simple template (minimal, lightweight)
await generatePdf({ ...options, template: 'simple' });

Custom CSS & HTML Injection

Override styles and add HTML blocks using any built-in template:

const result = await generatePdf({
  title: 'Styled Report',
  data, columns,
  template: 'modern',
  customCss: `
    .modern-table th {
      background: linear-gradient(135deg, #7c3aed, #2563eb) !important;
      color: #fff !important;
    }
  `,
  customHtml: {
    beforeTable: '<div style="padding: 12px; background: #f5f3ff;">Q4 2025 Summary</div>',
    afterTable: '<div style="font-size: 11px; color: #64748b;">Confidential</div>',
  },
});

Inline Template Function

Full control over the generated HTML:

const result = await generatePdf({
  title: 'Custom Report',
  data, columns,
  template: (params) => {
    const { title, data, columns } = params;

    const html = `
      <!DOCTYPE html>
      <html>
        <head><meta charset="UTF-8"><title>${title}</title></head>
        <body>
          <h1>${title}</h1>
          <table>
            <thead><tr>${columns.map(c => `<th>${c.title}</th>`).join('')}</tr></thead>
            <tbody>
              ${data.map(row =>
                `<tr>${columns.map(c => `<td>${row[c.dataIndex] ?? '—'}</td>`).join('')}</tr>`
              ).join('')}
            </tbody>
          </table>
        </body>
      </html>
    `;

    return { html, header: '', footer: '' };
  },
});

Registered Templates

Register a reusable template globally:

import { registerTemplate, generatePdf } from 'safeer-pdf-generator';

registerTemplate('invoice', (params) => {
  const { title, data, columns } = params;
  // ... return { html, header, footer }
});

// Use it by name anywhere
await generatePdf({ ...options, template: 'invoice' });

🌐 BYOB (Bring Your Own Browser)

Reuse a shared Puppeteer browser instance across multiple generations — saves memory and startup time:

import puppeteer from 'puppeteer';
import { generatePdf } from 'safeer-pdf-generator';

const browser = await puppeteer.launch({ headless: true });

// Generate multiple PDFs using the same browser
for (const report of reports) {
  await generatePdf({
    ...report,
    puppeteer: { browserInstance: browser },
  });
}

// You manage the browser lifecycle
await browser.close();

📡 Lifecycle Events

Listen for typed events during PDF generation:

import { generatePdf, PdfEventEmitter } from 'safeer-pdf-generator';

const events = new PdfEventEmitter();

events.onEvent('generation:started', ({ title, rowCount }) => {
  console.log(`Generating: ${title} (${rowCount} rows)`);
});

events.onEvent('generation:complete', ({ result }) => {
  console.log(`Done: ${result.pageCount} pages in ${result.durationMs}ms`);
});

events.onEvent('s3:upload:complete', ({ url }) => {
  notifyClient(url);
});

events.onEvent('error', ({ error, phase }) => {
  alertOps(`PDF failed at ${phase}: ${error.message}`);
});

await generatePdf({ ...options, events });

Available events: generation:started, generation:complete, chunk:processed, s3:upload:complete, email:sent, error

🔔 Webhook Dispatcher

Automatically POST to your backend when a PDF is ready. Includes HMAC-SHA256 signing and retry logic:

await generatePdf({
  ...options,
  webhook: {
    url: 'https://api.example.com/webhooks/pdf-ready',
    secret: process.env.WEBHOOK_SECRET,             // HMAC-SHA256 signing
    metadata: { tenantId: 'acme', userId: 'u-123' }, // Pass-through data
    timeoutMs: 10000,
  },
});

// Your endpoint receives:
// POST { s3Url, title, fileName, sizeBytes, pageCount, durationMs, metadata }
// Headers: { X-Webhook-Signature: "sha256=abc123..." }

☁️ S3 Upload & 📧 Email

const result = await generatePdf({
  title: 'Monthly Report',
  data: salesData,
  columns: [
    { key: 'name', title: 'Sales Rep', dataIndex: 'name' },
    { key: 'sales', title: 'Sales', dataIndex: 'sales' },
  ],
  s3: {
    bucket: 'my-pdfs',
    region: 'us-east-1',
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  },
  email: {
    to: '[email protected]',
    attachmentMode: 'link',
    smtp: {
      host: 'smtp.gmail.com',
      port: 587,
      secure: false,
      auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
    },
  },
});

console.log(`Uploaded: ${result.s3?.url}`);

🏗️ Framework Examples

Express.js

import express from 'express';
import { generatePdf } from 'safeer-pdf-generator';

const app = express();

app.post('/generate-report', async (req, res) => {
  try {
    const result = await generatePdf({
      title: req.body.title,
      data: req.body.data,
      columns: req.body.columns,
    });

    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `attachment; filename="${result.fileName}"`);
    res.send(result.buffer);
  } catch (error) {
    res.status(500).json({ error: 'PDF generation failed' });
  }
});

NestJS

import { Injectable } from '@nestjs/common';
import { generatePdf, PdfEventEmitter } from 'safeer-pdf-generator';

@Injectable()
export class PdfService {
  async createReport(data: any[], title: string) {
    const events = new PdfEventEmitter();
    events.onEvent('generation:complete', ({ result }) => {
      this.logger.log(`PDF ready: ${result.fileName}`);
    });

    return generatePdf({
      title,
      data,
      columns: [
        { key: 'id', title: 'ID', dataIndex: 'id' },
        { key: 'name', title: 'Name', dataIndex: 'name' },
      ],
      template: 'modern',
      events,
    });
  }
}

📊 Large Dataset Handling

import { generatePdf } from 'safeer-pdf-generator';

const result = await generatePdf({
  title: 'Large Report',
  data: largeDataset, // 100,000+ records
  columns,
  chunking: {
    enabled: true,
    chunkSize: 500,       // Rows per chunk
    maxConcurrency: 4,    // Parallel chunk processing
  },
});

🔀 PDF Merging

import { mergePdfs } from 'safeer-pdf-generator';

const merged = await mergePdfs({
  buffers: [buffer1, buffer2, buffer3],
});

🔧 Configuration Reference

PdfGenerationOptions

| Option | Type | Default | Description | |---|---|---|---| | title | string | required | Report title | | data | any[] | required | Array of data rows | | columns | ColumnDefinition[] | required | Column definitions | | template | string \| Function | 'default' | Template name or compiler function | | customCss | string | undefined | CSS injected into the template | | customHtml | { beforeTable?, afterTable? } | undefined | HTML injected around the table | | locale | string | 'en' | Locale ('en', 'ar' for RTL) | | userInfo | UserInfo | undefined | User info shown in header | | infoSection | InfoSection[] | undefined | Key-value info cards | | chunking | ChunkingOptions | { enabled: true, chunkSize: 100 } | Chunk processing config | | s3 | S3UploadConfig \| false | false | S3 upload configuration | | email | EmailSendConfig \| false | false | Email delivery configuration | | webhook | WebhookConfig \| false | false | Webhook dispatch configuration | | events | PdfEventEmitter | undefined | Lifecycle event emitter | | puppeteer | PuppeteerOptions | { headless: true } | Puppeteer launch options | | puppeteer.browserInstance | Browser | undefined | External browser (BYOB) | | hooks | HooksConfig | undefined | Sync lifecycle hooks | | logging | LoggingAdapter | noOpLogger | Logger implementation | | timeoutMs | number | 300000 | Global timeout (ms) |

ColumnDefinition

| Field | Type | Description | |---|---|---| | key | string | Unique column identifier | | title | string | Column header text | | dataIndex | string | Property path in data objects (supports nested: 'address.city') | | type | 'image' \| 'boolean' \| 'text' \| 'link' | Optional column type for custom rendering |

WebhookConfig

| Field | Type | Default | Description | |---|---|---|---| | url | string | required | Endpoint URL | | secret | string | undefined | HMAC-SHA256 secret | | metadata | Record<string, any> | undefined | Pass-through data | | timeoutMs | number | 10000 | Request timeout |

🌟 Why Choose safeer-pdf-generator?

  • Production Ready — Used in enterprise applications
  • Memory Efficient — Handles massive datasets without memory issues
  • Developer Friendly — Full TypeScript support with IntelliSense
  • Framework Agnostic — Works with any Node.js framework
  • Full Featured — Templates, S3, email, webhooks, events — all in one package
  • Active Maintenance — Regular updates and community support

📚 Examples

| Example | Description | |---|---| | simple-usage.js | Basic PDF generation | | custom-template.js | Custom CSS, inline templates, and registry | | byob-and-events.js | Browser reuse + lifecycle events | | webhook-integration.js | Webhook dispatch with HMAC signing | | express-app/ | Full Express.js integration | | nestjs-simplified/ | NestJS service example |

📄 License

MIT © Safeersoft

🔗 Links