Jspdf
jsPDF: client-side and server-side PDF generation in JavaScript, tables, images, custom fonts, autotable plugin
You are an expert in using jsPDF for generating PDF documents in JavaScript. ## Key Points - Always use `splitTextToSize()` before rendering text blocks to handle word wrapping within your content width — raw `text()` calls will overflow the page margin silently. - Track the current Y position manually and check it against page height before each block to insert page breaks, since jsPDF does not handle pagination automatically for freeform content. - Use the `compress: true` option in the constructor to reduce output file size significantly, especially for documents with embedded images. - Forgetting that jsPDF uses millimeters by default (when `unit: "mm"`) — passing pixel values produces microscopic or enormous output. Always match your coordinates to the configured unit. ## Quick Example ```bash # Browser or Node.js npm install jspdf jspdf-autotable # For Node.js image support npm install canvas ```
skilldb get document-generation-services-skills/JspdfFull skill: 247 linesjsPDF — Document Generation
You are an expert in using jsPDF for generating PDF documents in JavaScript.
Core Philosophy
Overview
jsPDF is a widely used library for generating PDF documents directly in JavaScript, both in the browser and in Node.js. Unlike pdf-lib which operates at the raw PDF specification level, jsPDF provides a higher-level drawing API similar to HTML Canvas — you call methods like text(), rect(), line(), and addImage() to build pages. The jspdf-autotable plugin adds automatic table generation with pagination. Choose jsPDF when you need a straightforward imperative API for building PDFs from scratch, especially for reports and invoices that combine text, tables, and images.
Setup & Configuration
# Browser or Node.js
npm install jspdf jspdf-autotable
# For Node.js image support
npm install canvas
import { jsPDF } from "jspdf";
import autoTable from "jspdf-autotable";
// Basic document creation
function createBasicPdf(): jsPDF {
// Orientation, unit, format
const doc = new jsPDF({
orientation: "portrait",
unit: "mm",
format: "a4", // 210 x 297 mm
compress: true,
});
return doc;
}
// Register a custom font (must be base64-encoded TTF)
import { readFileSync } from "fs";
function registerCustomFont(doc: jsPDF, fontPath: string, fontName: string): void {
const fontData = readFileSync(fontPath).toString("base64");
doc.addFileToVFS(`${fontName}.ttf`, fontData);
doc.addFont(`${fontName}.ttf`, fontName, "normal");
}
Core Patterns
Report with header, body text, and footer
interface ReportConfig {
title: string;
subtitle: string;
body: string[];
logoPath?: string;
}
function generateReport(config: ReportConfig): Buffer {
const doc = new jsPDF({ orientation: "portrait", unit: "mm", format: "a4" });
const pageWidth = doc.internal.pageSize.getWidth();
const pageHeight = doc.internal.pageSize.getHeight();
const margin = 20;
const contentWidth = pageWidth - margin * 2;
let yPos = margin;
// --- Header ---
doc.setFillColor(37, 99, 235); // blue
doc.rect(0, 0, pageWidth, 40, "F");
doc.setTextColor(255, 255, 255);
doc.setFontSize(22);
doc.text(config.title, margin, 20);
doc.setFontSize(12);
doc.text(config.subtitle, margin, 30);
// Reset text color
doc.setTextColor(0, 0, 0);
yPos = 55;
// --- Body paragraphs with automatic page breaks ---
doc.setFontSize(11);
for (const paragraph of config.body) {
const lines = doc.splitTextToSize(paragraph, contentWidth);
const blockHeight = lines.length * 6;
if (yPos + blockHeight > pageHeight - 30) {
addFooter(doc, pageWidth, pageHeight);
doc.addPage();
yPos = margin;
}
doc.text(lines, margin, yPos);
yPos += blockHeight + 8;
}
addFooter(doc, pageWidth, pageHeight);
return Buffer.from(doc.output("arraybuffer"));
}
function addFooter(doc: jsPDF, pageWidth: number, pageHeight: number): void {
const pageCount = doc.getNumberOfPages();
doc.setFontSize(9);
doc.setTextColor(128, 128, 128);
doc.text(
`Page ${pageCount}`,
pageWidth / 2,
pageHeight - 10,
{ align: "center" }
);
doc.text(
new Date().toLocaleDateString(),
pageWidth - 20,
pageHeight - 10,
{ align: "right" }
);
doc.setTextColor(0, 0, 0);
}
Tables with jspdf-autotable
interface SalesRow {
product: string;
region: string;
quantity: number;
revenue: number;
}
function generateSalesReport(data: SalesRow[]): Buffer {
const doc = new jsPDF();
doc.setFontSize(18);
doc.text("Quarterly Sales Report", 14, 22);
doc.setFontSize(10);
doc.setTextColor(100);
doc.text(`Generated: ${new Date().toISOString().slice(0, 10)}`, 14, 30);
doc.setTextColor(0);
autoTable(doc, {
startY: 38,
head: [["Product", "Region", "Quantity", "Revenue"]],
body: data.map((row) => [
row.product,
row.region,
row.quantity.toLocaleString(),
`$${row.revenue.toLocaleString("en-US", { minimumFractionDigits: 2 })}`,
]),
foot: [[
"Total", "",
data.reduce((s, r) => s + r.quantity, 0).toLocaleString(),
`$${data.reduce((s, r) => s + r.revenue, 0).toLocaleString("en-US", { minimumFractionDigits: 2 })}`,
]],
headStyles: { fillColor: [37, 99, 235], fontSize: 10 },
footStyles: { fillColor: [240, 240, 240], textColor: [0, 0, 0], fontStyle: "bold" },
alternateRowStyles: { fillColor: [248, 250, 252] },
styles: { fontSize: 9, cellPadding: 4 },
columnStyles: {
2: { halign: "right" },
3: { halign: "right" },
},
// Hook: add page numbers on every new page
didDrawPage: (data) => {
const pageCount = doc.getNumberOfPages();
doc.setFontSize(8);
doc.text(
`Page ${pageCount}`,
doc.internal.pageSize.getWidth() / 2,
doc.internal.pageSize.getHeight() - 8,
{ align: "center" }
);
},
});
return Buffer.from(doc.output("arraybuffer"));
}
Embedding images
import { readFileSync } from "fs";
function addImageToPdf(doc: jsPDF, imagePath: string, x: number, y: number, width: number): void {
const imageData = readFileSync(imagePath);
const base64 = imageData.toString("base64");
// Detect format from extension
const ext = imagePath.split(".").pop()?.toUpperCase();
const format = ext === "PNG" ? "PNG" : "JPEG";
doc.addImage(base64, format, x, y, width, 0); // height=0 preserves aspect ratio
}
Express endpoint
import express from "express";
const app = express();
app.use(express.json());
app.post("/api/report", (req, res) => {
try {
const pdf = generateReport(req.body);
res.setHeader("Content-Type", "application/pdf");
res.setHeader("Content-Disposition", 'attachment; filename="report.pdf"');
res.send(pdf);
} catch (err: any) {
res.status(500).json({ error: err.message });
}
});
Best Practices
- Always use
splitTextToSize()before rendering text blocks to handle word wrapping within your content width — rawtext()calls will overflow the page margin silently. - Track the current Y position manually and check it against page height before each block to insert page breaks, since jsPDF does not handle pagination automatically for freeform content.
- Use the
compress: trueoption in the constructor to reduce output file size significantly, especially for documents with embedded images.
Common Pitfalls
- Forgetting that jsPDF uses millimeters by default (when
unit: "mm") — passing pixel values produces microscopic or enormous output. Always match your coordinates to the configured unit. - Using
doc.save("file.pdf")in Node.js — this triggers a browser download dialog and fails server-side. In Node.js, useBuffer.from(doc.output("arraybuffer"))and write the buffer to disk or send it in a response.
Anti-Patterns
Using the service without understanding its pricing model. Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.
Hardcoding configuration instead of using environment variables. API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.
Ignoring the service's rate limits and quotas. Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.
Treating the service as always available. External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.
Coupling your architecture to a single provider's API. Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.
Install this skill directly: skilldb add document-generation-services-skills
Related Skills
Docraptor
"DocRaptor: HTML-to-PDF API, Prince XML engine, CSS print styles, headers/footers, page breaks, async documents"
Docusaurus
Docusaurus: React-based static site generator for documentation sites, versioned docs, MDX support, search integration, i18n
Latex Node
LaTeX with Node.js: compile LaTeX documents programmatically, template-based PDF generation, mathematical typesetting, academic papers
Markdoc
Markdoc: Stripe's Markdown-based authoring framework for structured documentation, custom tags, validation, and renderers
PDF Lib
"pdf-lib: create and modify PDFs in JavaScript, form filling, page manipulation, embedding images/fonts, digital signatures"
Puppeteer
"Puppeteer: headless Chrome, PDF generation from HTML, screenshots, web scraping, page automation, Chromium control"