Customizable invoice PDF generator for React, Vue, and vanilla JS - TypeScript, browser-first
A modern, lightweight TypeScript library for generating professional invoice PDFs directly in the browser. Perfect for web applications that need white-label invoice generation without server dependencies.
- π¨ Fully Customizable - Brand colors, logos, custom fields, and layouts
- π Framework Agnostic - Works with React, Vue, Angular, or vanilla JavaScript
- π± Browser-First - No server required, generates PDFs client-side
- π§ TypeScript Ready - Full type safety and IntelliSense support
- π Internationalization - Multi-language support with custom labels
- β Advanced Validation - Detailed validation with errors and warnings
- π Multiple Templates - Professional templates ready to use
- π― Lightweight - Minimal bundle impact with tree-shaking support
- π Live HTML Preview - Preview invoices before PDF generation
- π€ Multiple Export Formats - PDF, HTML, JSON, and CSV export
- β‘ Batch Processing - Generate multiple invoices efficiently
- π Plugin System - Extensible architecture for custom functionality
- π¨ Custom Templates - Build your own templates with the template builder
- π Progress Tracking - Real-time progress for batch operations
- π‘οΈ Schema Validation - Comprehensive data validation with detailed feedback
Package Manager | Command |
---|---|
npm | npm install invoice-craft |
yarn | yarn add invoice-craft |
pnpm | pnpm add invoice-craft |
bun | bun add invoice-craft |
import { generateInvoicePdf } from 'invoice-craft';
const invoiceData = {
from: { name: "Your Company", address: "123 Business St", email: "contact@company.com" },
to: { name: "Client Name", address: "456 Client Ave", email: "client@email.com" },
invoiceNumber: "INV-001",
invoiceDate: "2024-01-15",
items: [{ description: "Web Development", quantity: 40, unitPrice: 125.00, taxRate: 0.10 }],
currency: "USD"
};
const { blob, filename } = await generateInvoicePdf(invoiceData);
// Download logic here...
Type | Import Statement |
---|---|
ES Modules | import { generateInvoicePdf } from 'invoice-craft' |
CommonJS | const { generateInvoicePdf } = require('invoice-craft') |
Dynamic | const { generateInvoicePdf } = await import('invoice-craft') |
Option | Type | Description | Example |
---|---|---|---|
brandColor |
string | Primary brand color | "#3b82f6" |
logoUrl |
string | Company logo URL | "https://logo.png" |
layoutStyle |
string | Template style | "modern" , "minimal" , "creative" |
filenameTemplate |
function | Custom filename | ({ invoice }) => "inv-${invoice.invoiceNumber}.pdf" |
Language | Code | Status |
---|---|---|
English | en |
β Default |
Spanish | es |
β Available |
French | fr |
β Available |
German | de |
β Available |
Custom | - | β Configurable |
const spanishLabels = {
invoice: "Factura", invoiceNumber: "NΓΊmero de Factura",
date: "Fecha", total: "Total" /* ... more labels */
};
await generateInvoicePdf(invoiceData, { labels: spanishLabels });
Function | Purpose | Returns |
---|---|---|
validateInvoice() |
Basic validation | ValidationResult |
validateInvoiceStrict() |
Strict validation | ValidationResult |
Custom validation | Business rules | User-defined |
import { validateInvoice } from 'invoice-craft';
const result = validateInvoice(invoiceData);
if (!result.isValid) console.log(result.errors);
Framework | Status | Features | Example |
---|---|---|---|
React | β Full Support | Hooks, TypeScript, Components | View Example |
Vue 3 | β Full Support | Composition API, TypeScript | View Example |
Angular | β Compatible | Services, TypeScript | View Example |
Svelte | β Compatible | Stores, TypeScript | View Example |
Node.js | β Server-side | Express, API endpoints | View Example |
import { generateInvoicePdf, validateInvoice } from 'invoice-craft';
function InvoiceGenerator({ invoiceData }) {
const [isGenerating, setIsGenerating] = useState(false);
const handleGenerate = async () => {
setIsGenerating(true);
try {
const validation = validateInvoice(invoiceData);
if (!validation.isValid) throw new Error('Invalid data');
const { blob, filename } = await generateInvoicePdf(invoiceData);
// Download logic...
} catch (error) {
console.error(error);
} finally {
setIsGenerating(false);
}
};
return (
<button onClick={handleGenerate} disabled={isGenerating}>
{isGenerating ? 'Generating...' : 'Generate PDF'}
</button>
);
}
<template>
<div>
<button @click="generatePDF" :disabled="isGenerating">
{{ isGenerating ? 'Generating...' : 'Generate PDF' }}
</button>
<div v-if="validation && !validation.isValid">
<p v-for="error in validation.errors" :key="error.field">
{{ error.field }}: {{ error.message }}
</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { generateInvoicePdf, validateInvoice } from 'invoice-craft';
const isGenerating = ref(false);
const validation = ref(null);
const generatePDF = async () => {
isGenerating.value = true;
try {
validation.value = validateInvoice(invoiceData);
if (!validation.value.isValid) return;
const { blob, filename } = await generateInvoicePdf(invoiceData);
// Download logic...
} finally {
isGenerating.value = false;
}
};
</script>
import {
generateInvoicePdf,
generatePreviewHTML,
validateInvoice,
exportInvoice,
generateBatchInvoices,
createPlugin,
builtInPlugins
} from 'invoice-craft';
class InvoiceManager {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.setupEventListeners();
}
setupEventListeners() {
// Generate PDF
this.container.querySelector('#generate-pdf').addEventListener('click',
this.handleGeneratePDF.bind(this)
);
// Generate Preview
this.container.querySelector('#generate-preview').addEventListener('click',
this.handleGeneratePreview.bind(this)
);
// Export formats
this.container.querySelector('#export-html').addEventListener('click',
() => this.handleExport('html')
);
this.container.querySelector('#export-json').addEventListener('click',
() => this.handleExport('json')
);
}
async handleGeneratePDF() {
const button = this.container.querySelector('#generate-pdf');
const originalText = button.textContent;
try {
button.textContent = 'Generating...';
button.disabled = true;
// Validate first
const validation = validateInvoice(this.getInvoiceData());
if (!validation.isValid) {
this.showErrors(validation.errors);
return;
}
// Generate with plugins
const { blob, filename } = await generateInvoicePdf(this.getInvoiceData(), {
brandColor: '#3b82f6',
layoutStyle: 'modern',
plugins: [
builtInPlugins.currencyFormatter,
builtInPlugins.dateValidator,
this.createCustomPlugin()
]
});
this.downloadFile(blob, filename);
this.showSuccess(`Generated ${filename}`);
} catch (error) {
this.showError(error.message);
} finally {
button.textContent = originalText;
button.disabled = false;
}
}
async handleGeneratePreview() {
try {
const html = generatePreviewHTML(this.getInvoiceData(), {
theme: 'light',
responsive: true,
includeStyles: true
});
// Show in modal or iframe
this.showPreview(html);
} catch (error) {
this.showError(error.message);
}
}
async handleExport(format) {
try {
const result = await exportInvoice(this.getInvoiceData(), {
format,
includeStyles: format === 'html'
});
this.downloadExport(result);
} catch (error) {
this.showError(error.message);
}
}
createCustomPlugin() {
return createPlugin({
name: 'timestamp-plugin',
beforeRender: (invoice) => {
invoice.notes = `${invoice.notes}\n\nGenerated on ${new Date().toLocaleString()}`;
return invoice;
}
});
}
downloadFile(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
downloadExport(result) {
let url;
if (result.data instanceof Blob) {
url = URL.createObjectURL(result.data);
} else {
const blob = new Blob([result.data], { type: result.mimeType });
url = URL.createObjectURL(blob);
}
const a = document.createElement('a');
a.href = url;
a.download = result.filename;
a.click();
URL.revokeObjectURL(url);
}
showPreview(html) {
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.8); z-index: 1000;
display: flex; align-items: center; justify-content: center;
`;
const iframe = document.createElement('iframe');
iframe.srcdoc = html;
iframe.style.cssText = 'width: 90%; height: 90%; border: none; background: white;';
modal.appendChild(iframe);
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
document.body.appendChild(modal);
}
getInvoiceData() {
// Get invoice data from form or state
return {
from: {
name: "Your Company",
address: "123 Business St\nCity, State 12345",
email: "contact@company.com"
},
to: {
name: "Client Name",
address: "456 Client Ave\nClient City, State 67890"
},
invoiceNumber: "INV-001",
invoiceDate: new Date().toISOString().split('T')[0],
items: [
{
description: "Service",
quantity: 1,
unitPrice: 100.00,
taxRate: 0.1
}
],
currency: "USD"
};
}
showSuccess(message) {
this.showNotification(message, 'success');
}
showError(message) {
this.showNotification(message, 'error');
}
showErrors(errors) {
const messages = errors.map(e => `${e.field}: ${e.message}`).join('\n');
this.showNotification(messages, 'error');
}
showNotification(message, type) {
const notification = document.createElement('div');
notification.textContent = message;
notification.style.cssText = `
position: fixed; top: 20px; right: 20px; z-index: 1001;
padding: 12px 20px; border-radius: 6px; color: white;
background: ${type === 'success' ? '#10b981' : '#ef4444'};
`;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
}
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
new InvoiceManager('invoice-container');
});
// Batch processing example
async function processBatchInvoices(invoices) {
try {
const result = await generateBatchInvoices(invoices, {
concurrency: 3,
onProgress: (completed, total) => {
console.log(`Progress: ${completed}/${total}`);
updateProgressBar(completed / total * 100);
},
onError: (error, invoice, index) => {
console.error(`Failed to process invoice ${index}:`, error);
}
});
console.log(`Batch completed: ${result.summary.successful} successful, ${result.summary.failed} failed`);
// Download all successful PDFs
result.success.forEach((item, index) => {
setTimeout(() => {
const url = URL.createObjectURL(item.blob);
const a = document.createElement('a');
a.href = url;
a.download = item.filename;
a.click();
URL.revokeObjectURL(url);
}, index * 500);
});
} catch (error) {
console.error('Batch processing failed:', error);
}
}
function updateProgressBar(percentage) {
const progressBar = document.getElementById('progress-bar');
if (progressBar) {
progressBar.style.width = `${percentage}%`;
}
}
// server.js
import express from 'express';
import { generateInvoicePdf, validateInvoice } from 'invoice-craft';
import fs from 'fs/promises';
const app = express();
app.use(express.json());
// Generate invoice endpoint
app.post('/api/invoices/generate', async (req, res) => {
try {
const invoiceData = req.body;
// Validate invoice data
const validation = validateInvoice(invoiceData);
if (!validation.isValid) {
return res.status(400).json({
error: 'Invalid invoice data',
details: validation.errors
});
}
// Generate PDF
const { blob, filename } = await generateInvoicePdf(invoiceData, {
brandColor: '#3b82f6',
layoutStyle: 'modern'
});
// Convert blob to buffer for Node.js
const buffer = Buffer.from(await blob.arrayBuffer());
// Save to file system (optional)
await fs.writeFile(`./invoices/${filename}`, buffer);
// Send as response
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.send(buffer);
} catch (error) {
console.error('Invoice generation failed:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Batch generation endpoint
app.post('/api/invoices/batch', async (req, res) => {
try {
const { invoices } = req.body;
const result = await generateBatchInvoices(invoices, {
concurrency: 5,
continueOnError: true
});
// Save all successful PDFs
const savedFiles = [];
for (const item of result.success) {
const buffer = Buffer.from(await item.blob.arrayBuffer());
await fs.writeFile(`./invoices/${item.filename}`, buffer);
savedFiles.push(item.filename);
}
res.json({
success: true,
summary: result.summary,
files: savedFiles,
errors: result.errors.map(e => ({
index: e.index,
message: e.error.message
}))
});
} catch (error) {
console.error('Batch generation failed:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.listen(3000, () => {
console.log('Invoice server running on port 3000');
});
interface InvoiceData {
from: {
name: string;
address: string;
email?: string;
phone?: string;
logoUrl?: string;
brandColor?: string;
};
to: {
name: string;
address: string;
email?: string;
phone?: string;
};
invoiceNumber: string;
invoiceDate: string; // YYYY-MM-DD format
dueDate?: string; // YYYY-MM-DD format
items: InvoiceItem[];
currency: string; // ISO currency code (USD, EUR, etc.)
terms?: string;
notes?: string;
}
interface InvoiceItem {
description: string;
quantity: number;
unitPrice: number;
taxRate?: number; // 0.1 for 10% tax
}
interface GeneratePdfOptions {
brandColor?: string;
logoUrl?: string;
layoutStyle?: 'default' | 'modern' | 'minimal' | 'creative';
labels?: Partial<Labels>;
validate?: (invoice: InvoiceData) => void | ValidationResult;
filenameTemplate?: (context: { invoice: InvoiceData }) => string;
plugins?: InvoicePlugin[];
customTemplate?: CustomTemplate;
exportOptions?: ExportOptions;
}
interface ExportOptions {
format?: 'pdf' | 'html' | 'json' | 'csv';
compression?: boolean;
quality?: 'low' | 'medium' | 'high';
includeStyles?: boolean;
brandColor?: string;
logoUrl?: string;
layoutStyle?: 'default' | 'modern' | 'minimal' | 'creative';
}
interface ValidationResult {
isValid: boolean;
errors: ValidationError[];
warnings: ValidationError[];
}
interface ValidationError {
field: string;
message: string;
code: string;
severity: 'error' | 'warning';
}
interface BatchOptions {
concurrency?: number;
onProgress?: (completed: number, total: number) => void;
onError?: (error: Error, invoice: InvoiceData, index: number) => void;
continueOnError?: boolean;
}
interface BatchResult {
success: Array<{ blob: Blob; filename: string; index: number }>;
errors: Array<{ error: Error; invoice: InvoiceData; index: number }>;
summary: {
total: number;
successful: number;
failed: number;
};
}
interface CustomTemplate {
id: string;
name: string;
description?: string;
header: TemplateSection;
body: TemplateSection;
footer: TemplateSection;
styles: TemplateStyles;
supportedFeatures?: {
logo?: boolean;
brandColor?: boolean;
rtl?: boolean;
extraSections?: boolean;
};
}
interface TemplateSection {
content: string | ((data: any) => string);
styles?: TemplateStyles;
visible?: boolean | ((data: any) => boolean);
}
interface InvoicePlugin {
name: string;
version?: string;
beforeRender?: (invoice: InvoiceData) => InvoiceData | Promise<InvoiceData>;
afterRender?: (pdf: any) => any | Promise<any>;
beforeValidation?: (invoice: InvoiceData) => InvoiceData | Promise<InvoiceData>;
afterValidation?: (result: ValidationResult) => ValidationResult | Promise<ValidationResult>;
}
Use Case | Key Features | Implementation |
---|---|---|
E-commerce | Order β Invoice, Auto-validation | Transform order data, validate, generate |
SaaS Billing | Batch processing, Multiple formats | Monthly billing, API endpoints |
Multi-tenant | Custom branding, Plugins | Tenant-specific templates, dynamic branding |
Accounting | Validation, Export formats | Strict validation, CSV/JSON export |
Freelancing | Templates, Customization | Personal branding, custom layouts |
// E-commerce: Order to Invoice
const invoiceData = transformOrderToInvoice(order, customer);
const { blob } = await generateInvoicePdf(invoiceData, {
brandColor: process.env.BRAND_COLOR,
layoutStyle: 'modern'
});
// SaaS: Batch Billing
const result = await generateBatchInvoices(subscriptions, {
concurrency: 10,
onProgress: (completed, total) => updateProgress(completed, total)
});
// Multi-tenant: Custom Branding
const tenantPlugin = createPlugin({
name: 'tenant-branding',
beforeRender: (invoice) => applyTenantBranding(invoice, tenantId)
});
Feature | Function | Purpose | Example |
---|---|---|---|
HTML Preview | generatePreviewHTML() |
Live preview without PDF | generatePreviewHTML(data, {theme: 'light'}) |
Export Formats | exportInvoice() |
PDF, HTML, JSON, CSV | exportInvoice(data, {format: 'html'}) |
Batch Processing | generateBatchInvoices() |
Multiple invoices | generateBatchInvoices(invoices, {concurrency: 5}) |
Validation | validateInvoice() |
Data validation | validateInvoice(data) |
Plugins | createPlugin() |
Custom functionality | createPlugin({name: 'custom'}) |
Templates | createTemplate() |
Custom layouts | createTemplate('id', 'name') |
// HTML Preview
const html = generatePreviewHTML(invoiceData, {theme: 'light'});
// Export Formats
const result = await exportInvoice(invoiceData, {format: 'json'});
// Batch Processing
const batch = await generateBatchInvoices(invoices, {
concurrency: 3,
onProgress: (done, total) => console.log(`${done}/${total}`)
});
// Validation
const validation = validateInvoice(invoiceData);
if (!validation.isValid) console.log(validation.errors);
// Custom Plugin
const plugin = createPlugin({
name: 'timestamp',
beforeRender: (invoice) => {
invoice.notes += `\nGenerated: ${new Date().toLocaleString()}`;
return invoice;
}
});
// Custom Template
const template = createTemplate('modern', 'Modern Design')
.setHeader(data => `<h1>${data.invoice.from.name}</h1>`)
.build();
const options = {
filenameTemplate: ({ invoice }) =>
`invoice-${invoice.invoiceNumber}-${invoice.to.name.replace(/\s+/g, '-')}.pdf`
};
const { blob, filename } = await generateInvoicePdf(invoiceData, options);
// filename: "invoice-INV-001-Client-Name.pdf"
try {
const { blob, filename } = await generateInvoicePdf(invoiceData);
// Success - handle the PDF
} catch (error) {
if (error.message.includes('validation')) {
// Handle validation errors
console.error('Invalid invoice data:', error.message);
} else {
// Handle other errors
console.error('PDF generation failed:', error.message);
}
}
import { exportBatchInvoices } from 'invoice-craft';
const results = await exportBatchInvoices(invoices, {
format: 'pdf',
concurrency: 5,
onProgress: (completed, total) => {
const percentage = ((completed / total) * 100).toFixed(1);
console.log(`Progress: ${completed}/${total} (${percentage}%)`);
}
});
// Download all successful exports
results.success.forEach(result => {
const url = URL.createObjectURL(result.blob);
const a = document.createElement('a');
a.href = url;
a.download = result.filename;
a.click();
URL.revokeObjectURL(url);
});
git clone https://github.com/yourusername/invoice-craft.git
cd invoice-craft
npm install
npm run build
npm test
MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
- π Bug Reports: GitHub Issues
- π‘ Feature Requests: GitHub Discussions
- π Documentation: Wiki
Made with β€οΈ for developers who need reliable invoice PDF generation.