Skillshub puppeteer-pdf

Puppeteer PDF

install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/TerminalSkills/skills/puppeteer-pdf" ~/.claude/skills/comeonoliver-skillshub-puppeteer-pdf && rm -rf "$T"
manifest: skills/TerminalSkills/skills/puppeteer-pdf/SKILL.md
source content

Puppeteer PDF

Use headless Chrome to render HTML to PDF. Full CSS support — flexbox, grid, web fonts, media queries.

Setup

# Install Puppeteer. Downloads Chromium automatically.
npm install puppeteer

Basic HTML to PDF

// src/pdf/from-html.ts — Convert an HTML string to a PDF file.
// Puppeteer launches headless Chrome, sets the HTML content, and prints to PDF.
import puppeteer from "puppeteer";

export async function htmlToPdf(html: string, outputPath: string) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setContent(html, { waitUntil: "networkidle0" });

  await page.pdf({
    path: outputPath,
    format: "A4",
    margin: { top: "20mm", right: "15mm", bottom: "20mm", left: "15mm" },
    printBackground: true,
  });

  await browser.close();
}

URL to PDF

// src/pdf/from-url.ts — Navigate to a URL and save the page as PDF.
// Useful for archiving web pages or converting dashboards to reports.
import puppeteer from "puppeteer";

export async function urlToPdf(url: string, outputPath: string) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.goto(url, { waitUntil: "networkidle0" });
  await page.pdf({
    path: outputPath,
    format: "A4",
    printBackground: true,
  });

  await browser.close();
}

Template-Based Reports

// src/pdf/report-template.ts — Generate a styled report from data using an
// HTML template. Supports CSS grid layouts, charts, and branded headers.
import puppeteer from "puppeteer";

interface ReportData {
  title: string;
  date: string;
  rows: { label: string; value: string }[];
  logoUrl: string;
}

export async function generateReport(data: ReportData): Promise<Buffer> {
  const html = `
    <!DOCTYPE html>
    <html>
    <head>
      <style>
        /* Report styles — print-optimized with CSS Grid layout. */
        @page { margin: 20mm; }
        body { font-family: 'Segoe UI', sans-serif; color: #333; }
        .header { display: flex; justify-content: space-between; align-items: center;
                   border-bottom: 2px solid #3498db; padding-bottom: 10px; margin-bottom: 20px; }
        .header img { height: 40px; }
        .header h1 { font-size: 24px; color: #2c3e50; }
        table { width: 100%; border-collapse: collapse; margin-top: 20px; }
        th { background: #3498db; color: white; padding: 10px; text-align: left; }
        td { padding: 8px 10px; border-bottom: 1px solid #eee; }
        tr:nth-child(even) { background: #f8f9fa; }
        .footer { position: fixed; bottom: 0; width: 100%; text-align: center;
                   font-size: 10px; color: #999; }
      </style>
    </head>
    <body>
      <div class="header">
        <h1>${data.title}</h1>
        <img src="${data.logoUrl}" alt="Logo"/>
      </div>
      <p>Report generated: ${data.date}</p>
      <table>
        <tr><th>Metric</th><th>Value</th></tr>
        ${data.rows.map((r) => `<tr><td>${r.label}</td><td>${r.value}</td></tr>`).join("")}
      </table>
      <div class="footer">Confidential — ${data.title}</div>
    </body>
    </html>
  `;

  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: "networkidle0" });

  const pdf = await page.pdf({
    format: "A4",
    printBackground: true,
    margin: { top: "20mm", right: "15mm", bottom: "20mm", left: "15mm" },
  });

  await browser.close();
  return Buffer.from(pdf);
}

Headers and Footers

// src/pdf/header-footer.ts — Add dynamic headers and footers with page numbers.
// Puppeteer supports HTML templates for header/footer regions.
import puppeteer from "puppeteer";

export async function pdfWithPageNumbers(html: string, outputPath: string) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: "networkidle0" });

  await page.pdf({
    path: outputPath,
    format: "A4",
    printBackground: true,
    displayHeaderFooter: true,
    margin: { top: "30mm", bottom: "25mm", left: "15mm", right: "15mm" },
    headerTemplate: `
      <div style="font-size:9px; width:100%; text-align:center; color:#999;">
        Monthly Performance Report
      </div>`,
    footerTemplate: `
      <div style="font-size:9px; width:100%; text-align:center; color:#999;">
        Page <span class="pageNumber"></span> of <span class="totalPages"></span>
      </div>`,
  });

  await browser.close();
}

PDF Buffer for API Responses

// src/pdf/api.ts — Generate PDF in memory and return as buffer for API endpoints.
import puppeteer from "puppeteer";
import type { Request, Response } from "express";

export async function handlePdfRequest(req: Request, res: Response) {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setContent(`<h1>Invoice #${req.params.id}</h1>`, {
    waitUntil: "networkidle0",
  });

  const pdf = await page.pdf({ format: "A4", printBackground: true });
  await browser.close();

  res.contentType("application/pdf");
  res.send(pdf);
}