Invoice PDF generation is one of the most common requirements in Laravel apps — and one of the most frustrating to get right. The layout needs to look professional, the numbers need to be correct, and it needs to be downloadable, emailable, and storeable on demand.
This guide walks through a complete implementation: a Blade invoice template, a controller action that streams the PDF, and an email attachment — all using the HTML to PDF API so you don't need any server binaries.
The Blade Template
Start with a clean HTML invoice template. Use inline styles for maximum PDF compatibility:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
body { font-family: sans-serif; font-size: 13px; color: #111; margin: 0; padding: 32px; }
.header { display: flex; justify-content: space-between; margin-bottom: 40px; }
.logo { font-size: 22px; font-weight: 700; color: #2563eb; }
table { width: 100%; border-collapse: collapse; margin-top: 24px; }
th, td { padding: 10px 12px; border-bottom: 1px solid #e5e7eb; text-align: left; }
th { background: #f9fafb; font-weight: 600; }
.total { font-size: 16px; font-weight: 700; text-align: right; margin-top: 20px; }
</style>
</head>
<body>
<div class="header">
<span class="logo">Acme Inc.</span>
<div>
<strong>Invoice #{{ $invoice->number }}</strong><br>
Date: {{ $invoice->date->format('d M Y') }}
</div>
</div>
<p>Bill to: <strong>{{ $invoice->customer->name }}</strong></p>
<table>
<thead>
<tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Total</th></tr>
</thead>
<tbody>
@foreach ($invoice->items as $item)
<tr>
<td>{{ $item->description }}</td>
<td>{{ $item->quantity }}</td>
<td>{{ money($item->unit_price) }}</td>
<td>{{ money($item->total) }}</td>
</tr>
@endforeach
</tbody>
</table>
<div class="total">Total: {{ money($invoice->total) }}</div>
</body>
</html>The Controller
The controller renders the Blade view to HTML, sends it to the API, and streams the PDF response back:
<?php
namespace App\Http\Controllers;
use App\Models\Invoice;
use HtmlToPdfApi\Client;
use Illuminate\Http\Response;
class InvoiceController extends Controller
{
public function __construct(private Client $pdf) {}
public function download(Invoice $invoice): Response
{
$this->authorize('view', $invoice);
$html = view('invoices.pdf', compact('invoice'))->render();
$bytes = $this->pdf
->fromHtml($html)
->paperSize('a4')
->orientation('portrait')
->generate();
return response($bytes, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="invoice-' . $invoice->number . '.pdf"',
]);
}
}Attaching to an Email
To attach the PDF to a Laravel Mailable, generate the bytes first and pass them to attachData:
public function build(): self
{
$html = view('invoices.pdf', ['invoice' => $this->invoice])->render();
$bytes = app(Client::class)->fromHtml($html)->generate();
return $this->subject('Your Invoice #' . $this->invoice->number)
->view('emails.invoice')
->attachData($bytes, 'invoice.pdf', [
'mime' => 'application/pdf',
]);
}Storing in S3
Need to store invoices for later retrieval? Use Laravel Storage to push the PDF bytes to S3 or any configured disk:
Storage::disk('s3')->put(
"invoices/{$invoice->number}.pdf",
$bytes,
['ContentType' => 'application/pdf', 'ACL' => 'private']
);