saasykit/laravel-invoices
A Laravel package for creating and managing invoices, with models, migrations, line items, totals, and a straightforward API for adding invoice generation to your app's billing flows.
Reality check first. Documentation for this package is thin — the repo README, src/, config/, and the shipped Blade views are the documentation. Budget the first hour for reading source, not tutorials. Class/namespace names below follow the package's stated API shape; confirm exact names against the README and src/ before pasting anything into your codebase.
Install and publish (the 10-minute path):
composer require saasykit/laravel-invoicesphp artisan vendor:publish --provider="SaaSykit\LaravelInvoices\LaravelInvoicesServiceProvider" with tags for config and views.php artisan migrate and confirm the invoice/line-item tables landed.config/laravel-invoices.php — numbering format, storage disk, currency/locale, default template, tax defaults. This file is where 80% of your future configuration questions are answered.Where to look first, in order:
| File | Why it matters |
|---|---|
src/*ServiceProvider.php |
Container bindings, singletons (PDF renderer, builder), publishable paths — tells you what's injectable vs. facade-only. |
config/laravel-invoices.php |
Numbering, disk, currency, template paths, PDF driver options. |
resources/views/ (published) |
The invoice layout you'll be editing. This is your real template contract. |
src/Models/ |
The Eloquent models — relations, fillable, casts. Confirms how to attach invoices to your billable entities. |
First use case — render a one-off PDF without touching the database:
use SaaSykit\LaravelInvoices\Facades\Invoice; // confirm facade name
$pdf = Invoice::make()
->number('INV-2026-0001')
->currency('USD')
->from([...]) // seller details
->to([...]) // customer details
->addItem('Pro plan', 49.00, qty: 1)
->addItem('Seats x3', 9.00, qty: 3)
->tax(20) // or per-item tax, check README
->render(); // HTML or PDF depending on API
return $pdf->download('invoice.pdf');
Start here, not with persistence. Rendering is the core capability and the fastest way to see whether the shipped template and PDF engine meet your design requirements. Only after the layout is acceptable is it worth wiring persistence and numbering.
1. Separate "build" from "persist" from "render." The builder is fluent and stateful; the model is the durable record; the renderer is the side effect. Treat them as three steps in your own service class, not as one chained call inside a controller. That way a failed PDF render doesn't leave a half-created invoice row, and you can re-render historical invoices from stored data without replaying business logic.
2. Snapshot everything at issue time. This is the single most important pattern for invoices as legal/financial documents: when the invoice is finalized, copy seller details, customer details, line-item prices, tax rates, and totals into the invoice record — do not join back to users, plans, or prices at render time. Plans get renamed, addresses change, tax rates move. The invoice must render identically in three years. Treat the builder input as immutable once status = issued.
3. Attach to any billable via a polymorphic relation. The description explicitly targets "SaaS billing flows" and "attach to any billable entity." Add a morphMany on both User and Team/Workspace (or whatever your tenants are) rather than a user_id column. This avoids a migration the day you add team billing.
4. Numbering belongs in the database, not the config. Config gives you a format string; you still need atomicity. Generate numbers inside a transaction with a dedicated sequence table or lockForUpdate() on a counter row — never Invoice::max('number') + 1. Two concurrent checkout webhooks under a unique index will teach you this the hard way.
5. Two rendering paths, deliberately.
6. Queue bulk and recurring generation. Monthly invoice runs are CPU-bound on the PDF engine. Dispatch one job per invoice, store the file, and batch the emails separately. Don't render 500 PDFs in a single request; memory will find you.
7. Email via the mailer, but attach from disk. The description mentions mailer integration. If the package offers an InvoiceMail, prefer the pattern "store the PDF, then attach the stored file by path" over "render, then attach the in-memory string." It survives retries, keeps job payloads small, and lets you re-send without re-rendering.
8. Override templates by publishing, not by editing vendor. Publish the views, edit the copies, and pin the template path in config. Keep a diff of the shipped views per version so upgrades don't silently overwrite your branding.
9. Test with contract assertions, not just "it returns a PDF." Assert on the rendered content — assertSee('INV-2026-0001'), assertSee('$49.00'), totals line, tax line — and assert the PDF is non-empty and starts with %PDF. A package that "works" can still produce a blank page or wrong totals after a dependency bump.
10. Preview HTML in a route. Expose a dev-only route that renders the HTML variant of the invoice for the current user. It's the fastest debugging tool you'll have, and it's the same code path as the PDF minus the engine.
InvoiceService with 3–4 methods you control. When the package renames addItem to lineItem, you edit one file instead of forty.composer.json to see whether L11/L12 constraints were widened or dropped — dropping them strands you mid-migration.composer.json (dompdf/dompdf, mpdf/mpdf, etc.) and set memory_limit on the queue worker accordingly.{year} or {sequence} placeholder is not concurrency-safe on its own.config/laravel-invoices.php against the shipped default. New keys with sane defaults are fine; renamed keys fail silently as null. Same for published views.local, which is not public and may not survive container redeploys on ephemeral filesystems. Explicitly set the invoice disk (S3 or a mounted volume) in config; don't rely on the default.How can I help you explore Laravel packages today?