consoletvs/invoices
Laravel package for generating professional PDF invoices with customizable templates, line items, taxes, discounts, shipping, and currency support. A fluent API makes it easy to build, preview, and download invoices.
mpdf/mpdf or dompdf/dompdf (check the README for the version constraint your PHP/Laravel supports):composer require consoletvs/invoices
php artisan vendor:publish --provider="ConsoleTVs\Invoices\InvoicesServiceProvider"
Read config/invoices.php first. This is where the whole package is configured: the default template, the driver (mpdf or dompdf), the default currency symbol/code, and the disk used for saving. Everything else in the package is a thin builder over these values.
Learn the three classes you'll actually touch: ConsoleTVs\Invoices\Classes\Invoice, Buyer, and Seller. The Invoice is a fluent builder; Buyer/Seller are simple value objects for the party block.
Generate your first invoice from a controller. This is the canonical "hello world":
use ConsoleTVs\Invoices\Classes\Invoice;
use ConsoleTVs\Invoices\Classes\Buyer;
use ConsoleTVs\Invoices\Classes\Seller;
$invoice = (new Invoice)
->addItem('Consulting hours', 90, 3)
->setDiscount(10)
->setCurrency('EUR', '€')
->setSerial('INV-2024-001')
->setDate('2024-03-01')
->setDueDate('2024-03-31')
->setSeller(new Seller('Acme Ltd', '1 Main St', '+1 555 0100', '[email protected]'))
->setBuyer(new Buyer('Jane Doe', '42 Oak Ave', '+1 555 0199', '[email protected]'));
return $invoice->download('invoice.pdf'); // or ->show() / ->save($path)
If the exact fluent method names differ in your installed major version (the API has shifted between releases), open src/Classes/Invoice.php — every setter is a few lines and self-documenting. The facade Invoices::make(...) is a convenience wrapper around the same builder.
First real use case: a GET /invoices/{invoice}/pdf route that rebuilds an invoice from a database record and streams it. Start there rather than trying to build a full billing system on day one — this package is a renderer, not a billing engine.
Persist data, not just PDFs. The single most important pattern: store the invoice (serial, buyer details, line items, totals, currency, dates) in your own tables, and treat the package as a view layer. Regenerating on demand from the record lets you fix a template or a typo and reissue without a migration. Store the generated file only as a cache.
Wrap the builder in a service or factory. Build the seller once from config and centralise serial/date formatting:
class InvoiceFactory
{
public function fromRecord(InvoiceRecord $record): Invoice
{
$invoice = (new Invoice)
->setSerial($record->serial)
->setDate($record->issued_at->format('Y-m-d'))
->setDueDate($record->due_at->format('Y-m-d'))
->setCurrency($record->currency, $record->currency_symbol)
->setSeller(new Seller(
config('company.name'),
config('company.address'),
config('company.phone'),
config('company.email'),
))
->setBuyer(new Buyer($record->buyer_name, $record->buyer_address, $record->buyer_phone, $record->buyer_email));
foreach ($record->items as $item) {
$invoice->addItem($item->description, $item->unit_price, $item->quantity);
}
return $invoice->setDiscount($record->discount);
}
}
Every controller, job, and mailer then calls $factory->fromRecord($record)->download(), and your template logic lives in one place.
Archive to disk and serve from a signed route. Generate once, save to an explicit path, store the path, and expose it via a short-lived URL::temporarySignedRoute(...) instead of public storage. Keeps customer invoices non-guessable:
$path = storage_path("app/invoices/{$record->serial}.pdf");
$factory->fromRecord($record)->save($path);
Mail as an attachment. Generate to a temp file and attach it with Laravel's mailable, rather than trying to stream binary through the mailer:
Mail::to($record->buyer_email)->send(new InvoiceIssued($record, $path));
Queue bulk generation. PDF rendering (especially mPDF) is CPU-bound and memory-hungry. Month-end "generate all invoices" jobs should be queued with a per-job timeout, and ideally chunked so one bad invoice doesn't fail the batch.
Customise templates by publishing views. The package ships Blade templates; publish them (see the README's publish tag) and edit the HTML/CSS. If you need more than one look, either publish and select a template per invoice via the builder/setter, or copy the package template into your own namespace and render it yourself — it's just a Blade file fed the invoice data.
Test with the download assertion. In feature tests, assert the response is a PDF (assertDownload('invoice.pdf') where supported, or check the Content-Type and a %PDF prefix) and assert on your own factory output rather than parsing the binary.
mpdf/mpdf or dompdf/dompdf is installed and that driver in config/invoices.php matches. Run php artisan config:clear after editing it — a cached config silently ignores your change.public_path('logo.png')) or a data URI. Remote URLs are frequently blocked or silently dropped.1.234,56 and 1,234.56 across invoices is a real reconciliation problem.addItem/setDiscount cover a straightforward line-item model; VAT rules, per-line tax rates, partial payments, and credit notes are on you. Model those in your schema and either pre-compute totals before passing them in or extend the template.{{ }}, not {!! !!}). Custom published templates are the usual place HTML injection sneaks in.memory_limit on large invoices. Raise the limit for the generation job or run it in a queued worker with its own limits; don't let a PDF render take down a web request.How can I help you explore Laravel packages today?