invoice-craft
Version:
Customizable, browser-first invoice PDF generator library with modern TypeScript API
848 lines (697 loc) β’ 23.6 kB
Markdown
# π§Ύ Invoice Craft
> **Customizable invoice PDF generator for React, Vue, and vanilla JS - TypeScript, browser-first**
[](https://badge.fury.io/js/invoice-craft)
[](https://www.typescriptlang.org/)
[](https://opensource.org/licenses/MIT)
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
```typescript
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...
```
### 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 |
```typescript
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 |
```typescript
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](#react-example) |
| **Vue 3** | β
Full Support | Composition API, TypeScript | [View Example](#vue-example) |
| **Angular** | β
Compatible | Services, TypeScript | [View Example](#angular-example) |
| **Svelte** | β
Compatible | Stores, TypeScript | [View Example](#svelte-example) |
| **Node.js** | β
Server-side | Express, API endpoints | [View Example](#nodejs-example) |
### React Example
```tsx
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
```vue
<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
```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: "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}%`;
}
}
```
### Node.js Server Usage
```javascript
// 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
```typescript
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
```typescript
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
```typescript
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
```typescript
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
```typescript
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
```typescript
// 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
```typescript
// 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
```typescript
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
```typescript
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
```typescript
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
```bash
git clone https://github.com/Hamad-Center/invoice-craft.git
cd invoice-craft
npm install
npm run build
```
### Running Tests
```bash
npm test
```
## π License
MIT License - see [LICENSE](LICENSE) file for details.
## π€ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## π Support
- π **Bug Reports**: [GitHub Issues](https://github.com/Hamad-Center/invoice-craft/issues)
- π‘ **Feature Requests**: [GitHub Discussions](https://github.com/Hamad-Center/invoice-craft/discussions)
- π **Documentation**: [Wiki](https://github.com/Hamad-Center/invoice-craft/wiki)
---
Made with β€οΈ for developers who need reliable invoice PDF generation.