npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

invoice-craft

v1.2.1

Published

Customizable, browser-first invoice PDF generator library with modern TypeScript API

Readme

🧾 Invoice Craft

Customizable invoice PDF generator for React, Vue, and vanilla JS - TypeScript, browser-first

npm version TypeScript License: 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

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 build

Running Tests

npm test

📄 License

MIT License - see LICENSE file for details.

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📞 Support


Made with ❤️ for developers who need reliable invoice PDF generation.