invoice-craft
v1.2.1
Published
Customizable, browser-first invoice PDF generator library with modern TypeScript API
Maintainers
Readme
🧾 Invoice Craft
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.
✨ Features
Core Features
- 🎨 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
Advanced Features
- 🔍 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
🚀 Quick Start
Installation
| Package Manager | Command |
|----------------|---------|
| npm | npm install invoice-craft |
| yarn | yarn add invoice-craft |
| pnpm | pnpm add invoice-craft |
| bun | bun add invoice-craft |
Basic Usage
import { generateInvoicePdf } from 'invoice-craft';
const invoiceData = {
from: { name: "Your Company", address: "123 Business St", email: "[email protected]" },
to: { name: "Client Name", address: "456 Client Ave", email: "[email protected]" },
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...Module Support
| Type | Import Statement |
|------|-----------------|
| ES Modules | import { generateInvoicePdf } from 'invoice-craft' |
| CommonJS | const { generateInvoicePdf } = require('invoice-craft') |
| Dynamic | const { generateInvoicePdf } = await import('invoice-craft') |
🎨 Customization Options
Brand Customization
| 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" |
Internationalization Support
| 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 });Validation Options
| 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 Integration
Framework Support
| 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 |
React 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>
);
}Vue Example
<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>Modern Vanilla JavaScript
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: "[email protected]"
},
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}%`;
}
}Node.js Server Usage
// 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');
});📋 Complete API Reference
Core Interfaces
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;
}Export & Validation
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';
}Batch Processing
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;
};
}Template System
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);
}Plugin System
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>;
}💡 Common Usage Patterns
Integration Examples
| 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 |
Quick Implementation
// 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)
});� Advanced Features
Feature Overview
| 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') |
Quick Examples
// 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();🎯 Advanced Examples
Custom Filename Generation
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"Error Handling
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);
}
}Batch Export with Progress
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);
});🛠️ Development
Building from Source
git clone https://github.com/Hamad-Center/invoice-craft.git
cd invoice-craft
npm install
npm run buildRunning Tests
npm test📄 License
MIT License - see LICENSE file for details.
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📞 Support
- 🐛 Bug Reports: GitHub Issues
- 💡 Feature Requests: GitHub Discussions
- 📖 Documentation: Wiki
Made with ❤️ for developers who need reliable invoice PDF generation.
