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

barryvdh/laravel-dompdf

Laravel wrapper for Dompdf that turns HTML and Blade views into downloadable PDFs. Simple facade and config make it easy to add headers, footers, page numbers, and custom paper sizes to invoices, reports, and other documents.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the wrapper (it pulls in dompdf/dompdf transitively — don't require it separately or you'll get version drift):

composer require barryvdh/laravel-dompdf
php artisan vendor:publish --provider="Barryvdh\DomPDF\ServiceProvider" --tag=config

Publishing the config creates config/dompdf.php. Publish it, and read it top to bottom before your first render — it is the contract between the wrapper and the underlying Dompdf, and nearly every "why is my PDF wrong" issue traces back to a key in that file.

First use case — an invoice/receipt endpoint:

use Barryvdh\DomPDF\Facade\Pdf;

Route::get('/invoices/{invoice}/pdf', function (Invoice $invoice) {
    return Pdf::loadView('pdf.invoice', ['invoice' => $invoice])
        ->setPaper('a4', 'portrait')
        ->download("invoice-{$invoice->number}.pdf");
});

The facade is Barryvdh\DomPDF\Facade\Pdf (note: the old Barryvdh\DomPDF\Facade\PDF class was removed in 2.0; the Pdf alias is the one to use). loadView(), loadHTML(), and loadFile() all return the wrapper, so options chain. Render is deferred — nothing is actually processed until you call stream(), download(), save($path), or output().

Where to look first, in order: config/dompdf.php → the wrapper's PDF class (it implements __call magic forwarding to the real Dompdf instance) → the Dompdf docs for CSS support, since the wrapper adds nothing to the rendering engine itself.

Implementation Patterns

Keep rendering out of controllers. The wrapper is a thin builder; wrap it in a dedicated class so views, paper size, and options live in one place:

namespace App\Support;

use Barryvdh\DomPDF\Facade\Pdf;

class InvoicePdf
{
    public function render(Invoice $invoice): string
    {
        return Pdf::loadView('pdf.invoice', [
                'invoice' => $invoice->load('lines'),
                'company' => config('company'),
            ])
            ->setPaper('a4')
            ->setOption(['enable_remote' => false, 'defaultFont' => 'DejaVu Sans'])
            ->output();                      // raw bytes
    }
}

Return the right response type. download() sets Content-Disposition: attachment, stream() inlines, save() writes to a local filesystem path (so use Storage::disk('local')->path(...), not a URL). output() returns a string, which is what you want for mail attachments:

$message->attachData($pdf->output(), 'invoice.pdf', ['mime' => 'application/pdf']);

Generate heavy documents on a queue. A 200-page report will blow the web request's memory and time limit. Push a job, write to the local disk, then serve it with a signed URL or Storage::download().

Page numbers and headers/footers. Margin boxes (@page { @bottom-center { ... } }) aren't reliably supported. Use the inline PHP script block, which does not require enable_php:

<script type="text/php">
    if (isset($pdf)) {
        $pdf->page_text(270, 810, "Page {PAGE_NUM} of {PAGE_COUNT}", null, 9, [0.4, 0.4, 0.4]);
    }
</script>

Note the wrapper's page_text / page_script handling changed in the Dompdf 2.0 upgrade; if you're maintaining older code that hooks these, retest after upgrading.

Options. setOption(['key' => 'value']) merges into the existing config; the old setOptions() (full replace) is deprecated and its behaviour flip-flopped across 2.1.0 → 2.1.1 → 2.2.0. Use setOption() with the same snake_case keys as config/dompdf.php (enable_remote, default_paper_orientation, font_dir, chroot, …), not the camelCase Dompdf names.

Testing. Assert on status, content type, and disposition — the response is a plain Response, not a BinaryFileResponse, so assertDownload() won't work:

$response = $this->actingAs($user)->get("/invoices/{$invoice->id}/pdf");

$response->assertOk();
$response->assertHeader('content-type', 'application/pdf');
$this->assertStringContainsString('attachment; filename=invoice-', $response->headers->get('content-disposition'));

For unit-level tests, Pdf::fake() doesn't exist — assert on the service instead, or assert output() starts with %PDF-.

Gotchas and Tips

The wrapper is shared state within a request. It's registered as a singleton (dompdf.wrapper), so options set on one document persist into the next render in the same request/loop. If you generate several PDFs in one action, set paper and every option explicitly on each chain, or app()->forgetInstance('dompdf.wrapper') between renders.

enable_remote is false by default as of 3.0. Remote images and stylesheets silently disappear rather than erroring. Point url() at public_path() (the public_path option was added for exactly this), or inline assets as data URIs. Only flip enable_remote on for trusted content — it's SSRF-capable, which is why v3 added allowedRemoteHosts.

data:// and v3.1. Dompdf 3.1 routes data URIs through protocol validation. Default config includes data://, but if you published config/dompdf.php in an earlier version, add data:// to allowed_protocols yourself or every embedded base64 image will vanish after the upgrade. Same class of trap: dompdf.defines was renamed to dompdf.options, and top-level orientation was removed in favour of options.default_paper_orientation.

CSS support is CSS 2.1-ish. No flexbox, no grid, no position: sticky. Layout with tables and floats. Use break-before: page / page-break-inside: avoid to control pagination, and give table headers <thead> so Dompdf repeats them across pages.

Fonts need a writable cache. Dompdf writes JSON font metrics into font_dir/font_cache (default under storage/fonts). Permission errors there surface as blank glyphs or silent fallback to Helvetica. For non-Latin text or consistent metrics, ship a TTF and declare it with @font-face { src: url(".../Inter.ttf") } using an absolute local path, and set defaultFont to match.

chroot restricts local file access to base_path() by default. Referencing /var/www/shared/logo.png will fail; keep assets inside the project or add a setOption(['chroot' => ...]).

Long documents. ini_set('memory_limit', '512M') and set_time_limit(120) around the render, or move to a queue. Rendering is CPU-bound single-threaded — parallelise with multiple workers, not threads.

Upgrade discipline. Pin the wrapper major version. The 2.x line required Dompdf 2.x, 3.x requires Dompdf 3.x and drops PHP < 8.1 / Laravel < 9 — a composer update can take you across a breaking change (facade class removal, option renames, callback signature changes) without any code touching your repo. Diff config/dompdf.php against the published copy on every major bump; that's where the breaking changes land.

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