flutter_pdf_invoice_widgets

pub package License: MIT Support on Ko-fi

A comprehensive Flutter package providing 10 highly customizable PDF invoice and document templates.

#Flutter #Dart #PDF #Invoice #InvoiceGenerator #PDFGeneration #DocumentTemplates #OpenSource

The package is UI-independent, performs no network requests, and returns PDF bytes that can be saved, returned from a server, previewed, printed, or shared.

Features

  • Ten built-in templates: Classic, Modern, Professional, Minimalist, Creative, Receipt, Proforma, Quote/Estimate, Delivery Note, and Credit Note.
  • Typed, schema-versioned InvoiceDocument model with JSON round trips.
  • Centralized rounded calculations for line tax/discount, tax-inclusive prices, document discounts, fees, shipping, withholding, payments, and balance due.
  • Typed validation with stable issue codes and useful field paths.
  • Custom columns, stable label keys, per-column alignment, and independent table border controls.
  • Locale-aware currency, number, percentage, and date formatting.
  • LTR/RTL page direction, Arabic and English label bundles, custom Unicode fonts, and font fallback lists.
  • Logos, generated or image-based QR codes, watermarks, signatures, metadata, custom sections, progress reporting, and cooperative cancellation.
  • Multi-page tables with repeated headers and stress coverage for 150-line documents across all templates.
  • Pure Dart runtime compatible with Flutter, command-line, server, desktop, and web environments supported by the underlying pdf package.

Requirements

  • Dart >=3.12.0 <4.0.0.
  • pdf ^3.13.0 and intl ^0.20.0.

The Dart minimum follows the current pdf dependency's minimum. Flutter applications can use the package normally; Flutter is not forced into server or command-line consumers.

Installation

dependencies:
  flutter_pdf_invoice_widgets: ^1.0.1

Then run dart pub get or flutter pub get.

Quick start

import 'dart:io';

import 'package:flutter_pdf_invoice_widgets/flutter_pdf_invoice_widgets.dart';

Future<void> main() async {
  final document = InvoiceDocument(
    type: InvoiceDocumentType.modernInvoice,
    company: CompanyInfo(
      name: 'Acme Studio',
      city: 'Riyadh',
      country: 'Saudi Arabia',
      taxId: 'VAT-123456789',
    ),
    customer: CustomerInfo(name: 'Example Customer'),
    info: InvoiceInfo(
      invoiceNumber: 'INV-2026-001',
      invoiceDate: DateTime(2026, 8, 9),
      dueDate: DateTime(2026, 9, 8),
      currency: 'SAR',
      currencySymbol: 'SAR ',
      statusValue: InvoiceStatus.unpaid,
      terms: 'Payment is due within 30 days.',
    ),
    items: [
      InvoiceItem(
        description: 'Flutter development',
        quantity: 24,
        unit: 'hours',
        unitPrice: 250,
        taxRate: 15, // 15 means 15%.
      ),
    ],
    payment: PaymentInfo(
      paymentMethods: ['Bank transfer', 'Card'],
      qrData: 'https://example.com/pay/INV-2026-001',
    ),
  );

  final template = InvoiceTemplateRegistry().create(document);
  final bytes = await template.generate();
  await File('invoice.pdf').writeAsBytes(bytes);
}

See the complete runnable example.

Templates

Document type Direct class Default character
classicInvoice ClassicInvoice Traditional bordered invoice
modernInvoice ModernInvoice Contemporary color header
professionalInvoice ProfessionalInvoice Corporate layout
minimalistInvoice MinimalistInvoice Typography-first layout
creativeInvoice CreativeInvoice Bold colors and shapes
receipt ReceiptTemplate Dynamically sized receipt roll
proformaInvoice ProformaInvoice Proforma watermark and validity
quoteEstimate QuoteEstimate Customer acceptance section
deliveryNote DeliveryNote Shipping data and received markers
creditNote CreditNote Credit reference and message

Image Samples

Classic invoice

Classic invoice PDF sample

Modern invoice

Modern invoice PDF sample

The registry is recommended for data-driven applications. Direct construction remains supported:

final bytes = await ClassicInvoice(
  company: CompanyInfo(name: 'Seller'),
  customer: CustomerInfo(name: 'Buyer'),
  info: InvoiceInfo(
    invoiceNumber: 'INV-1',
    invoiceDate: DateTime.now(),
  ),
  items: [
    InvoiceItem(description: 'Service', quantity: 1, unitPrice: 100),
  ],
).generate();

Financial calculations

InvoiceCalculator is the single authoritative calculation path used by the document model and every built-in template. With the default policy, calculated line values and aggregates are rounded to two decimal places.

final document = InvoiceDocument(
  company: company,
  customer: customer,
  info: info,
  items: items,
  taxInclusive: false,
  monetaryPolicy: const MonetaryPolicy(decimalPlaces: 2),
  adjustments: [
    InvoiceAdjustment(
      label: 'Customer discount',
      type: InvoiceAdjustmentType.discount,
      calculation: InvoiceAdjustmentCalculation.percentage,
      value: 10,
    ),
    InvoiceAdjustment(
      label: 'Shipping',
      type: InvoiceAdjustmentType.shipping,
      value: 25,
    ),
    InvoiceAdjustment(
      label: 'Processing fee',
      type: InvoiceAdjustmentType.fee,
      value: 5,
    ),
  ],
  amountPaid: 50,
);

print(document.totals.grandTotal);
print(document.totals.balanceDue);

Calculation order:

  1. Quantity × unit price is rounded for each line.
  2. The line discount is rounded and subtracted.
  3. Exclusive tax is added, or inclusive tax is extracted.
  4. Rounded lines are aggregated.
  5. Document adjustments are calculated from the post-line-tax total.
  6. Discounts and withholding are subtracted; shipping and fees are added.
  7. Amount paid is subtracted to produce balance due.

When both a rate and explicit amount are supplied for the same line tax or discount, validation reports an ambiguity. Choose one representation.

Credit Note signs

The package never silently changes signs. Use positive values when the Credit Note title communicates the business meaning, or negative lines when your accounting convention requires signed exports. Lines and totals keep the values you supplied.

Validation

The registry validates documents automatically. Validation can also be performed before rendering:

final issues = document.validate();
for (final issue in issues) {
  print('${issue.code}: ${issue.field} — ${issue.message}');
}

document.validateOrThrow();

Validation covers required text, date order, finite numbers, percentage ranges, ambiguous tax/discount inputs, and adjustment values. Empty item lists are valid and render a table header with zero totals.

Styling

import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;

final style = InvoiceStyle(
  primaryColor: PdfColors.indigo,
  accentColor: PdfColors.amber,
  backgroundColor: PdfColors.white,
  pageFormat: PdfPageFormat.a4,
  pageMargin: const pw.EdgeInsets.all(40),
  headerStyle: const HeaderStyle(
    showLogo: true,
    showCompanyName: true,
    showCompanyDetails: true,
    padding: pw.EdgeInsets.all(12),
  ),
  footerStyle: const FooterStyle(
    showPageNumbers: true,
    showDate: true,
    customText: 'Confidential',
  ),
  tableStyle: TableStyle(
    showBorder: true,
    showHeaderBorder: true,
    showRowBorders: false,
  ),
  watermarkText: 'DRAFT',
  signatureLabel: 'Authorized signature',
);

Presets are available through InvoiceStyle.classic(), .modern(), .dark(), and .colorful(). Page backgrounds are painted by every template, including the dark preset.

HeaderStyle controls the shared header used by Classic, Proforma, Quote, Delivery Note, and Credit Note. Modern, Professional, Minimalist, Creative, and Receipt have document-specific headers; their logo/company layout is controlled by the company data and the template design. FooterStyle applies to all multi-page templates; Receipt has a document-specific ending instead.

Custom columns and labels

Column IDs are stable label and data keys. Unknown IDs read from InvoiceItem.customColumns.

final style = InvoiceStyle(
  labels: {
    'description': 'Service',
    'quantity': 'Hours',
    'project': 'Project',
  },
  tableStyle: TableStyle(
    columns: [
      InvoiceColumn.index(),
      InvoiceColumn.description(),
      InvoiceColumn(id: 'project', header: 'Project', flex: 2),
      InvoiceColumn.quantity(),
      InvoiceColumn.unitPrice(),
      InvoiceColumn.tax(),
      InvoiceColumn.total(),
    ],
  ),
);

final item = InvoiceItem(
  description: 'Implementation',
  quantity: 10,
  unitPrice: 100,
  customColumns: {'project': 'Mobile app'},
);

Use InvoiceColumn.formatter for calculated custom text. Per-column alignment is honored for headers and cells.

Localization, Unicode, and RTL

final rtlStyle = InvoiceStyle(
  localization: InvoiceLocalization.arabic(),
  primaryFont: arabicRegularFont,
  boldFont: arabicBoldFont,
  fontFallback: [fallbackFont],
  labels: {'invoice': 'فاتورة ضريبية'},
);

InvoiceLocalization controls locale-aware dates/currency, text direction, and built-in labels. Custom labels override the bundle.

Important: PDF standard fonts do not cover Arabic and many Unicode scripts. This package does not bundle or download fonts. Load appropriately licensed TTF fonts from bytes and provide them explicitly:

final regular = PdfFontLoader.fromBytes(regularTtfBytes);
final bold = PdfFontLoader.fromBytes(boldTtfBytes);

final style = InvoiceStyle(
  primaryFont: regular,
  boldFont: bold,
  fontFallback: [regular],
  localization: InvoiceLocalization.arabic(),
);

In Flutter, obtain the bytes with rootBundle.load; on Dart/server, use File.readAsBytes, an asset system, or your own storage layer. Fonts are never fetched over the network implicitly.

JSON persistence

import 'dart:convert';

final encoded = jsonEncode(document.toJson());
final restored = InvoiceDocument.fromJson(
  (jsonDecode(encoded) as Map).cast<String, Object?>(),
);

The schema includes a version and rejects unknown versions. Dates use UTC ISO 8601 strings; images use base64; enums use stable names. Runtime-only values such as PDF fonts, callbacks, and style objects are intentionally not serialized.

Metadata and custom sections

import 'package:pdf/widgets.dart' as pw;

final template = InvoiceTemplateRegistry().create(
  document,
  metadata: const InvoicePdfMetadata(
    title: 'Invoice INV-2026-001',
    subject: 'Consulting services',
    keywords: 'invoice,consulting',
  ),
  beforeItemsBuilder: (context) => [
    pw.Text('Project: Phoenix', style: context.style.boldBodyTextStyle),
    pw.SizedBox(height: 12),
  ],
  afterItemsBuilder: (context) => [
    pw.Text('Internal reference: PO-42'),
  ],
);

For a completely custom design, extend BaseTemplate or register a custom factory with InvoiceTemplateRegistry.register.

Progress and cancellation

var cancelled = false;

final bytes = await template.generate(
  onProgress: (value) => print('${(value * 100).round()}%'),
  isCancelled: () => cancelled,
);

Cancellation is checked before validation, before layout, and before saving. PDF layout itself is synchronous in the underlying library; use an isolate for large documents when UI responsiveness matters.

Flutter preview, print, and share

Add the printing package to the consuming Flutter application, not to this core package:

import 'package:printing/printing.dart';

await Printing.layoutPdf(onLayout: (_) async => pdfBytes);
await Printing.sharePdf(bytes: pdfBytes, filename: 'invoice.pdf');

This keeps Flutter-only dependencies out of server applications.

Performance and visual review

  • Run dart run benchmark/generate_large_invoice.dart for a reproducible 1,000-line generation benchmark.
  • Run dart run tool/generate_catalog.dart to generate the same reference data through every built-in template under build/catalog.
  • Follow RELEASE_CHECKLIST.md to render and visually inspect the catalog before publishing.

Resize high-resolution images before passing them to the package; embedded image bytes affect memory use and PDF size. maxPages defaults to 100 and can be increased explicitly for unusually large documents.

Scope and compliance

Version 1.0.0 does not claim PDF encryption, cryptographic signatures, PDF/A, tagged-PDF accessibility, or jurisdiction-specific tax/e-invoicing compliance. QR payloads are rendered exactly as supplied. Consult qualified specialists and official specifications before using generated documents for regulated flows.

Quality checks

The repository runs formatting, static analysis, tests, examples, stress generation, and a publish dry-run. CI covers the minimum and latest supported Dart SDKs. See MIGRATION.md, RELEASE_CHECKLIST.md, and SECURITY.md.

License

MIT License. See LICENSE.