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 Invoices Laravel Package

finller/laravel-invoices

Generate professional invoices in Laravel with a fluent, model-driven API. Build PDF or HTML invoices with line items, taxes and discounts, and customize output through drivers and Blade views. Includes serial numbers and multiple invoice formats.

View on GitHub
Deep Wiki
Context7

Getting Started

Requirements. Laravel 10+ and PHP 8.2+. Recent versions render PDFs through spatie/laravel-pdf, which means you also need Node and a Chromium binary available to PHP — this is the single biggest setup hurdle. In Docker, install chromium, fonts-liberation, and fonts-noto in the image (see Gotchas).

composer require finller/laravel-invoices

php artisan vendor:publish --tag="invoices-config"
php artisan vendor:publish --tag="invoices-migrations"
php artisan vendor:publish --tag="invoices-views"   # Blade templates for the PDF

php artisan migrate

If you use the Browsershot/Puppeteer driver:

npm install puppeteer
npx puppeteer browsers install chrome

Where to look first. Open config/invoices.php before writing any code. It controls the storage disk, the seller/buyer/item model classes you can swap, the serial-number format, the default Blade template, and the PDF driver. Then read the published migrations — they tell you exactly which columns exist (serial_number, status, seller, buyer, items, due_at, paid_at) and therefore what the fluent API can set. The published views in resources/views/vendor/laravel-invoices are your layout starting point.

First use case. Persist an invoice with line items and hand the buyer a PDF:

use Finller\Invoice\Models\Invoice;
use Finller\Invoice\Models\InvoiceItem;

$invoice = Invoice::make()
    ->seller($company)              // your biller
    ->buyer($customer)              // the billed party
    ->addItem(
        InvoiceItem::make()
            ->name('Consulting — March')
            ->quantity(12)
            ->unitPrice(9500)       // minor units, see config
            ->vatRate(20)
    )
    ->dueAt(now()->addDays(30))
    ->save();                       // allocates a serial number + persists

return $invoice->pdf()->download("{$invoice->serial_number}.pdf");

The chain is fluent and every setter maps to a column; confirm exact method names against the README for the version you install, since the API moved between v1 and v2.

Implementation Patterns

Wrap creation in an action class, always inside a transaction. Serial numbers are allocated on save, and you do not want a half-written invoice with a consumed number if an item insert fails.

final class IssueInvoice
{
    public function handle(Order $order): Invoice
    {
        return DB::transaction(function () use ($order) {
            $invoice = Invoice::make()
                ->seller($order->merchant->billingProfile)
                ->buyer($order->customer->billingProfile)
                ->dueAt(now()->addDays($order->paymentTerms()));

            foreach ($order->lines as $line) {
                $invoice->addItem(InvoiceItem::make()
                    ->name($line->description)
                    ->quantity($line->qty)
                    ->unitPrice($line->unit_price_minor)
                    ->vatRate($line->vat_rate));
            }

            return tap($invoice)->save();
        });
    }
}

Generate and store the PDF out-of-band. Rendering is seconds, not milliseconds — don't do it in the request that creates the invoice. Store to a private disk and keep the path on the invoice (or a documents table):

final class RenderInvoicePdf implements ShouldQueue
{
    public function handle(InvoicePdfRenderer $renderer): void
    {
        $path = "invoices/{$this->invoice->serial_number}.pdf";

        Storage::disk(config('invoices.disk'))
            ->put($path, $renderer->html($this->invoice)->toPdf());

        $this->invoice->update(['pdf_path' => $path]);
    }
}

Serve it through a signed, authorized route — never a public URL, invoices leak customer data.

Snapshot the seller and buyer onto the invoice. Store the address, VAT number, and legal name as they were at issue time. If you reference the Company model live, last year's PDFs silently mutate when someone edits the billing address, which is both a support nightmare and a compliance problem.

Use one InvoiceSerie per document type and fiscal year. Quotes, invoices, credit notes, and proformas must each have their own gapless sequence. Allocate the serial at finalize time, not when the draft is created, so abandoned drafts don't burn numbers.

Drive status transitions explicitly. Model draft → pending → paid (or cancelled) in your domain, not by mutating the column ad hoc. Credit notes should be a separate invoice with a negative total referencing the original, not a deletion.

Custom branding via published views. Copy resources/views/vendor/laravel-invoices into your app, edit the Blade files, and point config('invoices.template') at yours — or store a template key per invoice if you support multiple brands. Keep layout in Blade, and move the "which template / which logo / which footer" decision into config or a per-tenant setting.

Testing. Fake PDF rendering so the suite doesn't shell out to Chromium: Pdf::fake() (from spatie/laravel-pdf) and assert on the HTML/Blade output instead. Assert on serial numbers, totals, VAT breakdown, and rounding — snapshot the rendered HTML for a golden-file test.

Gotchas and Tips

  • Chromium is the #1 failure. "Could not find Chrome" or a 60s timeout on first render usually means the binary isn't where Browsershot expects, or the node_modules path is wrong in a container. Set the executable path explicitly in the config, and install fonts in the image — otherwise €, £, or Cyrillic glyphs render as tofu boxes in an otherwise "working" PDF.
  • Amounts are integers. Store minor units (cents) and let the package format them. Passing floats produces 0.30000000000000004 lines on totals once VAT and discounts stack. Verify line-level vs document-level rounding with a 3×€0.333 style test.
  • Serial number races. Two concurrent requests on a fresh serie can collide. lockForUpdate() on the serie row inside the transaction, or rely on the package's increment plus a unique index on (serie_id, serial_number) so collisions fail loudly instead of silently duplicating.
  • Gaps in numbering. A rolled-back transaction after the increment leaves a hole. If your jurisdiction requires gapless sequences, allocate the number as the last step, after everything else has succeeded.
  • This is a renderer, not a compliance tool. No Factur-X/ZUGFeRD XML, no XRechnung, no Peppol, no VAT validation, no archiving/retention. If you need e-invoicing in the EU, this package produces the human-readable half — budget for a separate e-invoicing layer or a provider.
  • Pin the version and keep the generated PDFs. The v1→v2 jump changed the PDF backend. Re-rendering a three-year-old invoice after a composer update will not reproduce the same document. Persist the byte stream, not just the parameters.
  • Clear caches after template changes. php artisan view:clear && php artisan config:clear, otherwise you'll debug a layout that isn't the one being rendered.
  • Swap models through config, not by editing vendor code. Extend the base Invoice/InvoiceItem Eloquent models, keep the table names, and register your classes in config/invoices.php. Same for the storage disk — use Storage::disk(...), never public_path().
  • Debug with HTML, not PDFs. Render the Blade view to a string and open it in a browser to iterate on layout; reserve actual PDF generation for the final check. It's an order of magnitude faster.
  • Locale and currency formatting come from the app locale and the invoice currency. Set both before rendering — a de_DE locale with a USD invoice quietly produces the wrong separators.
  • Maintenance risk. 237 stars and a solo maintainer: read the migrations and the Invoice model source once rather than treating it as a black box, and keep your own InvoicePdfRenderer interface around it so you can swap the engine without rewriting call sites.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource