A powerful PDF generation library for Node.js with first-class TypeScript support
- 🚀 Generate PDFs from HTML templates with multiple template engines (Handlebars, EJS, or plain HTML)
- ⚡ Template pre-compilation and caching for better performance
- 📦 Works with both JavaScript and TypeScript
- ✨ Supports both CommonJS and ES Modules
- 🎨 Customize PDF options (format, orientation, borders, etc.)
- 🔥 Async/await support
- 📝 Type definitions included
- 🛠️ CLI support
- Installation
- Quick Start
- JavaScript Usage
- TypeScript Usage
- API Reference
- Template Guide
- CLI Usage
- Contributing
- License
# Using npm
npm install pdf-node
# Using yarn
yarn add pdf-node
# Using pnpm
pnpm add pdf-nodeBy default, pdf-node includes support for Handlebars. To use EJS templates, you'll need to install the EJS package:
# Install EJS for EJS template support
npm install ejs @types/ejsThis package includes TypeScript type definitions. For the best experience, install these dev dependencies:
npm install --save-dev typescript @types/node @types/handlebars<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>User Report</title>
<style>
body {
font-family: Arial, sans-serif;
}
.user {
margin-bottom: 20px;
padding: 10px;
border: 1px solid #eee;
}
.header {
background: #f5f5f5;
padding: 20px;
text-align: center;
}
</style>
</head>
<body>
<div class="header">
<h1>User Report</h1>
<p>Generated on {{date}}</p>
</div>
{{#each users}}
<div class="user">
<h2>{{name}}</h2>
<p>Age: {{age}}</p>
<p>Email: {{email}}</p>
</div>
{{/each}}
</body>
</html>// CommonJS
const {generatePDF} = require('pdf-node');
const fs = require('fs');
const path = require('path');
// Or ES Modules
// import { generatePDF } from 'pdf-node';
// import { fileURLToPath } from 'url';
// import { dirname } from 'path';
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = dirname(__filename);
async function createPDF() {
// Read HTML template
const html = fs.readFileSync(path.join(__dirname, 'template.html'), 'utf8');
// Sample data
const users = [
{name: 'John Doe', age: 30, email: 'john@example.com'},
{name: 'Jane Smith', age: 25, email: 'jane@example.com'}
];
// PDF options
const options = {
format: 'A4',
orientation: 'portrait',
border: '10mm',
header: {
height: '15mm',
contents:
'<div style="text-align: center;">Confidential Report</div>'
},
footer: {
height: '15mm',
contents: {
default:
'<div style="text-align: center; color: #666;">Page {{page}} of {{pages}}</div>'
}
}
};
// Generate PDF
try {
const result = await generatePDF({
html: html,
data: {
users: users,
date: new Date().toLocaleDateString()
},
path: './user-report.pdf',
type: 'pdf',
pdfOptions: options
});
console.log('PDF generated successfully:', result.filename);
} catch (error) {
console.error('Error generating PDF:', error);
}
}
createPDF();import {generatePDF, PDFOptions} from 'pdf-node';
import * as fs from 'fs';
import * as path from 'path';
import {fileURLToPath} from 'url';
// For ES Modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Define TypeScript interfaces
interface User {
name: string;
age: number;
email: string;
}
interface TemplateData {
users: User[];
date: string;
}
async function generateUserReport() {
// Read HTML template
const html = fs.readFileSync(path.join(__dirname, 'template.html'), 'utf8');
// Sample typed data
const users: User[] = [
{name: 'John Doe', age: 30, email: 'john@example.com'},
{name: 'Jane Smith', age: 25, email: 'jane@example.com'}
];
// PDF options with TypeScript type
const options: PDFOptions = {
format: 'A4',
orientation: 'portrait',
border: '10mm',
header: {
height: '15mm',
contents:
'<div style="text-align: center;">Confidential Report</div>'
},
footer: {
height: '15mm',
contents: {
default:
'<div style="text-align: center; color: #666;">Page {{page}} of {{pages}}</div>'
}
}
};
// Generate PDF with buffer output
try {
const result = await generatePDF({
html: html,
data: {
users: users,
date: new Date().toLocaleDateString()
},
type: 'pdf',
buffer: true, // Get PDF as buffer
pdfOptions: options
});
// Example: Save buffer to file
if ('buffer' in result) {
fs.writeFileSync('./user-report-buffer.pdf', result.buffer);
console.log('PDF generated from buffer');
}
// Or use the file path if not using buffer
if ('filename' in result) {
console.log('PDF generated at:', result.filename);
}
} catch (error) {
console.error('Error generating PDF:', error);
}
}
generateUserReport();-
Step 2 - Create your HTML Template
<!DOCTYPE html> <html lang="en"></html>
-
{{#each users}}
- Name: {{this.name}}
- Age: {{this.age}}
{{/each}}
-
Step 3 - Provide format and orientation as per your need
"height": "10.5in", // allowed units: mm, cm, in, px
"width": "8in", // allowed units: mm, cm, in, px
- or -
"format": "Letter", // allowed units: A3, A4, A5, Legal, Letter, Tabloid
"orientation": "portrait", // portrait or landscape
var options = { format: 'A3', orientation: 'portrait', border: '10mm', header: { height: '45mm', contents: '<div style="text-align: center;">Author: Shyam Hajare</div>' }, footer: { height: '28mm', contents: { first: 'Cover page', 2: 'Second page', // Any page number is working. 1-based index default: '<span style="color: #444;">{{page}}</span>/<span>{{pages}}</span>', // fallback value last: 'Last Page' } } };
-
Step 4 - Provide HTML, user data and output configuration
var users = [ { name: 'alpha', age: '21' }, { name: 'beta', age: '23' }, { name: 'gamma', age: '29' } ]; // For file output (default) var document = { html: html, data: { users: users }, path: './output.pdf', type: 'pdf' }; // For buffer output (useful for APIs/web services) var documentBuffer = { html: html, data: { users: users }, type: 'pdf', buffer: true // Enable buffer output };
-
Step 5- After setting all parameters, just pass document and options to
pdfmethod.pdf.generatePDF(document, options) .then(res => { console.log(res); }) .catch(error => { console.error(error); });
-
Step 5 (Alternative) - Generate PDF (Buffer Output)
pdf.generatePDF(documentBuffer, options) .then(res => { console.log('Buffer size:', res.size, 'bytes'); console.log('Content type:', res.type); // Use res.buffer for HTTP responses, saving to database, etc. }) .catch(error => { console.error(error); });
pdf-node supports multiple template engines out of the box:
Handlebars is the default template engine. It provides a simple syntax for inserting data into templates.
EJS allows you to embed JavaScript directly in your templates.
<h1><%= title %></h1>
<ul>
<% items.forEach(function(item) { %>
<li><%= item.name %> - $<%= item.price.toFixed(2) %></li>
<% }); %>
</ul>For simple use cases, you can use plain HTML with basic variable substitution:
<h1>Static Report</h1>
<p>This is a static HTML template that will be converted to PDF.</p>For better performance, templates are compiled and cached by default. You can control this behavior using the cacheTemplate option:
// Disable caching for this template
const result = await generatePDF({
html: template,
data: {
/* ... */
},
engine: 'handlebars', // or 'ejs' or 'html'
cacheTemplate: false // Disable caching
// ...other options
});
// Clear the template cache if needed
import {clearTemplateCache} from 'pdf-node';
clearTemplateCache();You can pass any JavaScript object as template data. The data will be available in your templates according to the template engine's syntax.
const data = {
title: 'Sales Report',
date: new Date().toLocaleDateString(),
items: [
{name: 'Product A', price: 19.99},
{name: 'Product B', price: 29.99}
],
total: 49.98
};The buffer output feature is perfect for web APIs and services where you need to return PDF data directly to clients without saving temporary files:
// Express.js API example
const express = require('express');
const pdf = require('pdf-node');
const app = express();
app.get('/generate-report/:userId', async (req, res) => {
try {
// Your HTML template
const html = `
<h1>User Report</h1>
<p>Report for user: {{user.name}}</p>
<p>Generated on: {{date}}</p>
`;
// Document configuration for buffer output
const document = {
html: html,
data: {
user: {name: 'John Doe'},
date: new Date().toLocaleDateString()
},
type: 'pdf',
buffer: true // Enable buffer output
};
// Generate PDF buffer
const result = await pdf.generatePDF(document);
// Set appropriate headers and send buffer
res.setHeader('Content-Type', 'application/pdf');
res.setHeader(
'Content-Disposition',
'attachment; filename="report.pdf"'
);
res.setHeader('Content-Length', result.size);
res.send(result.buffer);
} catch (error) {
res.status(500).json({error: 'Failed to generate PDF'});
}
});const document = {
html: htmlTemplate,
data: templateData,
type: 'pdf',
path: './output.pdf' // Required for file output
};
// Returns: { filename: '/path/to/output.pdf' }const document = {
html: htmlTemplate,
data: templateData,
type: 'pdf',
buffer: true // Enable buffer output
};
// Returns: { buffer: Buffer, size: number, type: 'application/pdf' }You can manually insert a page break in your PDF using the addNewPage function:
var pdf = require('pdf-node');
// Add a page break in your HTML template
var html = `
<h1>Page 1 Content</h1>
<p>This is on the first page.</p>
${pdf.addNewPage()}
<h1>Page 2 Content</h1>
<p>This is on the second page.</p>
`;const pdf = require('pdf-node');
const fs = require('fs');
// Generate invoice PDF and save to database
async function generateInvoiceBuffer(invoiceData) {
const htmlTemplate = fs.readFileSync('./templates/invoice.html', 'utf8');
const document = {
html: htmlTemplate,
data: invoiceData,
type: 'pdf',
buffer: true
};
const result = await pdf.generatePDF(document);
// Save buffer to database (example with MongoDB)
await db.collection('invoices').insertOne({
invoiceId: invoiceData.id,
pdfData: result.buffer,
size: result.size,
createdAt: new Date()
});
return result;
}const express = require('express');
const pdf = require('pdf-node');
const app = express();
app.get('/api/report/:format', async (req, res) => {
const {format} = req.params; // 'download', 'inline', 'base64'
const document = {
html: reportTemplate,
data: reportData,
type: 'pdf',
buffer: true
};
try {
const result = await pdf.generatePDF(document);
switch (format) {
case 'download':
res.setHeader('Content-Type', 'application/pdf');
res.setHeader(
'Content-Disposition',
'attachment; filename="report.pdf"'
);
res.send(result.buffer);
break;
case 'inline':
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'inline');
res.send(result.buffer);
break;
case 'base64':
res.json({
pdf: result.buffer.toString('base64'),
size: result.size,
type: result.type
});
break;
default:
res.status(400).json({error: 'Invalid format'});
}
} catch (error) {
res.status(500).json({error: 'PDF generation failed'});
}
});// PDF Generation Microservice
const express = require('express');
const pdf = require('pdf-node');
const app = express();
app.post('/generate-pdf', async (req, res) => {
const {template, data, options = {}} = req.body;
try {
const document = {
html: template,
data: data,
type: 'pdf',
buffer: true
};
const result = await pdf.generatePDF(document);
// Return buffer as base64 for JSON response
res.json({
success: true,
pdf: result.buffer.toString('base64'),
metadata: {
size: result.size,
type: result.type,
generatedAt: new Date().toISOString()
}
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});| Property | Type | Required | Description |
|---|---|---|---|
html |
String | ✅ | HTML template string (supports Handlebars syntax) |
data |
Object | ✅ | Data to inject into the template |
type |
String | ❌ | Output type ('pdf' or 'buffer'). Default: 'pdf' |
path |
String | Required for file output. Absolute or relative path | |
buffer |
Boolean | Set to true for buffer output. Cannot use with path |
|
engine |
String | ❌ | Template engine to use ('handlebars', 'ejs', or 'html'). Default: 'handlebars' |
cacheTemplate |
Boolean | ❌ | Whether to cache the compiled template. Default: true |
pdfOptions |
Object | ❌ | Options for PDF generation (see html-pdf for details) |
{
filename: '/absolute/path/to/output.pdf';
}{
buffer: Buffer, // PDF data as Node.js Buffer
size: 25600, // Size in bytes
type: 'application/pdf' // MIME type
}try {
const result = await pdf.generatePDF(document);
console.log('PDF generated successfully');
} catch (error) {
if (error.message.includes('options are missing')) {
console.error('Invalid document configuration');
} else if (error.message.includes('Path is required')) {
console.error('Path required when buffer is not enabled');
} else if (error === 'only pdf file type supported') {
console.error('Invalid document type');
} else {
console.error('PDF generation failed:', error.message);
}
}- Buffer output is ideal for small to medium PDFs (< 50MB)
- File output is better for large PDFs to avoid memory issues
- Always handle buffers promptly to prevent memory leaks
- Cache compiled Handlebars templates for repeated use
- Use streaming for large file downloads
- Consider PDF compression for smaller file sizes
- Sanitize user input in templates to prevent XSS
- Validate file paths to prevent directory traversal
- Implement rate limiting for PDF generation endpoints
Please refer to the following if you want to use conditions in your HTML template: