To generate a PDF in Laravel 13, install barryvdh/laravel-dompdf with Composer, render a Blade view with Pdf::loadView('pdf.invoice', $data), and return ->download('invoice.pdf') to send it to the browser. You can also ->stream() it inline or ->output() the raw bytes and save them to a storage disk.
DomPDF converts HTML and CSS into PDF using pure PHP, with no external binary. That makes it easy to run on shared hosting, Docker and queue workers. The trade-off is limited CSS support, so I keep PDF templates simple and table-based.
Prerequisites
- Laravel 13.x on PHP 8.3 or newer.
barryvdh/laravel-dompdf3.x, which supports Laravel 9 through 13 and usesdompdf/dompdf3.x.- The PHP
domandmbstringextensions (enabled on most installs).gdis needed for most image formats.
Step 1: Install the package
composer require barryvdh/laravel-dompdfThe service provider and the Pdf facade are auto-discovered. To change defaults such as paper size or font directory, publish the config file:
php artisan vendor:publish --provider="Barryvdh\DomPDF\ServiceProvider"This creates config/dompdf.php.
Step 2: Create a Blade template for the PDF
Create resources/views/pdf/invoice.blade.php. Put the CSS in a style tag in the head, and include the UTF-8 meta tag so accented characters render correctly:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
body { font-family: "DejaVu Sans", sans-serif; font-size: 12px; color: #222; }
h1 { font-size: 20px; margin-bottom: 4px; }
table { width: 100%; border-collapse: collapse; margin-top: 16px; }
th, td { border-bottom: 1px solid #ddd; padding: 6px; text-align: left; }
.right { text-align: right; }
</style>
</head>
<body>
<h1>Invoice #{{ $invoice->number }}</h1>
<p>Billed to: {{ $invoice->customer_name }}<br>Date: {{ $invoice->issued_at->format('d M Y') }}</p>
<table>
<thead>
<tr><th>Item</th><th class="right">Qty</th><th class="right">Price</th></tr>
</thead>
<tbody>
@foreach ($invoice->items as $item)
<tr>
<td>{{ $item->description }}</td>
<td class="right">{{ $item->quantity }}</td>
<td class="right">{{ number_format($item->price, 2) }}</td>
</tr>
@endforeach
</tbody>
</table>
<p class="right"><strong>Total: {{ number_format($invoice->total, 2) }}</strong></p>
</body>
</html>DejaVu Sans ships with DomPDF and covers far more characters than the core PDF fonts like Helvetica.
Step 3: Generate and download the PDF
<?php
namespace App\Http\Controllers;
use App\Models\Invoice;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Response;
class InvoicePdfController extends Controller
{
public function download(Invoice $invoice): Response
{
$invoice->load('items');
return Pdf::loadView('pdf.invoice', ['invoice' => $invoice])
->setPaper('a4', 'portrait')
->download("invoice-{$invoice->number}.pdf");
}
public function show(Invoice $invoice): Response
{
$invoice->load('items');
// Opens the PDF in the browser tab instead of downloading it.
return Pdf::loadView('pdf.invoice', ['invoice' => $invoice])
->stream("invoice-{$invoice->number}.pdf");
}
}Register the routes in routes/web.php, behind authentication and authorization since invoices are private:
Route::middleware('auth')->group(function () {
Route::get('/invoices/{invoice}/pdf', [InvoicePdfController::class, 'download'])
->name('invoices.pdf')
->can('view', 'invoice');
});How do I save the PDF to storage instead of downloading it?
Use output() to get the PDF as a string and store it on any filesystem disk. This also works in queued jobs, for example to attach the file to an email later:
use Illuminate\Support\Facades\Storage;
$pdf = Pdf::loadView('pdf.invoice', ['invoice' => $invoice]);
Storage::disk('local')->put("invoices/{$invoice->number}.pdf", $pdf->output());How do I change paper size, orientation and options?
setPaper() accepts sizes such as a4, letter or legal and an orientation of portrait or landscape. Other DomPDF options can be set per document:
Pdf::setOption(['dpi' => 150, 'defaultFont' => 'DejaVu Sans'])
->loadView('pdf.report', $data)
->setPaper('a4', 'landscape');Remote resources (images or CSS loaded over HTTP) are disabled by default for security, through the isRemoteEnabled option. Rather than enabling it, reference local files with an absolute path such as public_path('images/logo.png').
Where should PDF generation live in a larger app?
Controllers stay cleaner when the PDF logic sits in a small service class, for example an InvoicePdf class with a render(Invoice $invoice): string method that returns $pdf->output(). The controller then only decides whether to download, stream or store the result, and a queued job can reuse the same class to build monthly statements overnight. Keep the Blade templates in their own resources/views/pdf folder and do not reuse your normal web layout: the web layout loads Vite assets and JavaScript that DomPDF cannot use, and the print layout usually needs different spacing and fonts anyway. Pass fully prepared data into the view so the template only formats values and never runs queries.
Common mistakes
- Using modern CSS. Flexbox and grid are not supported. Use tables, floats and simple block layout.
- Linking assets with URLs. With remote access off,
asset()URLs do not load. Use filesystem paths. - Squares instead of characters. Core fonts lack many glyphs. Switch to DejaVu Sans or embed your own font.
- Huge reports in a web request. Large PDFs take time and memory. Generate them in a queued job and notify the user when ready.
- N+1 queries in the template. Eager load relations, as in
$invoice->load('items'), before rendering.
How to verify it works
Open the route in the browser and check the file. For an automated check, assert on the response headers in a Pest test:
<?php
use App\Models\Invoice;
it('downloads an invoice as a pdf', function () {
$invoice = Invoice::factory()->hasItems(2)->create();
$this->actingAs($invoice->user)
->get(route('invoices.pdf', $invoice))
->assertOk()
->assertHeader('content-type', 'application/pdf')
->assertDownload("invoice-{$invoice->number}.pdf");
});The factory relationship and the policy are assumptions about your app; adjust them to your models.
FAQ
Is DomPDF good enough for complex layouts?
For invoices, receipts, certificates and simple reports, yes. For pixel-perfect output with modern CSS, a headless-browser based tool is a better fit.
Can I add page numbers?
Yes. Enable PHP in the DomPDF options and use its page script API, or add a fixed-position footer in CSS. Only enable PHP execution for templates you fully control.
Can I email the generated PDF?
Yes. Attach the stored file to a Mailable with Attachment::fromStorage(), or attach the raw output with Attachment::fromData().



