Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Pdf Invoices Laravel Package

akira/laravel-pdf-invoices

A Laravel package for generating PDF invoices from Eloquent models, with customizable templates, line items, taxes, discounts, and multi-language support. Stream or download invoices directly from your app, or save them to storage. Designed to simplify billing and document workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a young package (v0.x-era maturity signals: ~39 stars, zero dependents, last release 2026-05-11). Treat the API as promising but not yet frozen and read the source before you commit to it in anything long-lived.

Install

composer require akira/laravel-pdf-invoices
php artisan vendor:publish --provider="Akira\LaravelPdfInvoices\PdfInvoicesServiceProvider"

Confirm the platform first. The package requires PHP 8.4 and Laravel 12+. If your app is on 8.2/8.3 or Laravel 11, stop here — it will not install, and forcing --ignore-platform-reqs will just move the failure to runtime.

Where to look first, in order

  1. README.md — for a package this new, the README is the specification. Check whether the usage examples match the actual method signatures.
  2. config/pdf-invoices.php after publishing — invoice number series, default currency/tax, PDF driver, view paths, storage disk.
  3. src/ root — find the entry point (typically a facade or a Invoice::make()-style builder) and read the builder class end to end. In a builder-pattern package, the builder is the whole API surface.
  4. src/Data/ or equivalent — the immutable data objects. These tell you the real model of the domain: what a line item holds, how tax is represented, whether money is stored as minor units.
  5. resources/views/ — the default PDF template. You will almost certainly override it.

First use case (the 15-minute win)

Generate a single invoice from an existing Eloquent model and stream it — no persistence, no numbering, no queue:

use Akira\LaravelPdfInvoices\Facades\Invoice;

return Invoice::make()
    ->number('INV-0001')
    ->date(now())
    ->seller(/* immutable seller data object */)
    ->buyer(/* immutable buyer data object */)
    ->item('Consulting', quantity: 10, unitPrice: 15000) // minor units
    ->download('invoice.pdf');

The goal of this first pass is not correctness — it is to confirm the PDF driver actually renders on your machine (fonts, currency glyphs, memory) before you build anything around it. Do that on day one; discovering a missing Unicode font after you've wired the package into a billing flow is miserable.

Implementation Patterns

Isolate the builder behind an application-level action. The builder is convenient but it is third-party API surface — don't sprinkle it through controllers. Wrap it once:

final class GenerateInvoicePdf
{
    public function __invoke(Order $order): string
    {
        return Invoice::make()
            ->fromOrder($order)   // your mapping, not the package's
            ->render();
    }
}

When the package ships a breaking change (likely — see Gotchas), you edit one file. This also gives you a place to assert totals before handing data to the renderer.

Compose the data objects explicitly, don't pass Eloquent models. Immutable DTOs are the package's core design bet. Feed them plain values from your models via a dedicated mapper (OrderToInvoiceData). Benefits: the invoice is a snapshot, not a live view of a mutable row; and you can unit-test the mapper without a DB.

Persist the rendered payload, not just the inputs. For anything legally meaningful (tax invoices, receipts), generate the PDF once and store it (S3/local disk) alongside the invoice number and a JSON snapshot of the input data. Regenerating from live models months later produces a different document if a customer address or tax rate changed. Storage-then-serve is also much faster for repeat downloads:

Storage::disk('invoices')->put($path, $pdf);
$invoice->update(['pdf_path' => $path]);

Treat numbering as a database concern, not a builder concern. Whatever ->number() takes, generate it in a transaction with a locked counter (or a dedicated invoice_sequences table keyed by year + series). Do not derive numbers from count() or model IDs — gaps and collisions on rollback are the classic failure here, and many jurisdictions require gapless sequences.

Queue anything bulk. PDF rendering is CPU- and memory-bound. A GenerateInvoiceJob per order, with ->timeout(120) and a retry policy, is the right shape for month-end runs. Stream/download inline only for single on-demand documents.

Test the data layer, not the bytes. Assert on your mapper and on computed totals (subtotal, per-line tax, discount order-of-operations, grand total) with exact minor-unit values. For the PDF itself, assert only that rendering returns a non-empty buffer with a %PDF- header. Byte-for-byte PDF snapshots are brittle and will fail on every driver or library bump.

Multi-currency and per-line tax. If the package supports a per-item tax rate, keep currency on the invoice header and never mix currencies within one document — convert upstream. Decide and document your rounding policy once (round per line, then sum, is the common convention) and test it at a boundary like quantity 3 × €33.335.

Gotchas and Tips

Money in floats will bite you. Check whether the API expects integer minor units or a decimal string. Whichever it is, standardise on it in your mapper and never pass $model->total straight through if that column is a float. Rounding errors surface as one-cent mismatches on the PDF that nobody notices until an accountant does.

Immutability means reassignment, not mutation. With immutable data objects, calls like $item->tax(20) may return a new instance rather than modifying in place. If your invoice comes out with zero tax, that's almost certainly why — you dropped the return value. Look for readonly properties and with*-style methods in src/.

Unicode and currency glyphs. Default PDF drivers (dompdf and friends) ship a limited font set. €, £, ₦, and non-Latin customer names are the usual casualties — you get blank boxes or missing characters. Register a TTF with full coverage in config and re-test your first-use-case PDF with a non-ASCII buyer name before going further.

Driver memory and timeouts. dompdf will silently OOM or time out on large invoices (hundreds of line items) or when memory_limit is low. Browsershot-based drivers need Node and Chrome present in the container. Pin your driver choice deliberately and document it in the deploy image.

Config caching in production. After php artisan config:cache, changes to the published config are invisible until you re-cache. If invoice numbering or the template path "won't change," this is why.

Template overrides need republishing on upgrade. If the package ships views, your published copies become stale on version bumps — new fields or layout fixes won't appear. Note the override path in your upgrade checklist.

Zero dependents is a real signal. With no downstream users, edge cases around odd tax configurations, credit notes, or discount stacking are unlikely to have been exercised. Budget time for reading the source when something behaves unexpectedly, and pin the version in composer.json ("akira/laravel-pdf-invoices": "^0.x") rather than floating.

Extension points to check for early: custom Blade/PDF template path in config, a renderer/driver interface you can bind your own implementation to, and any events fired around generation (useful for audit logging). If there is no renderer abstraction, the package is a leaf dependency — keep it that way and don't build abstractions on top of it that assume one will appear.

Fallback plan. Given the maturity level, keep your GenerateInvoicePdf action thin enough that swapping in laravel-dompdf + your own Blade template is a half-day job, not a rewrite. The immutable-DTO-then-render shape described above is deliberately the part you own.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core