tarfin-labs/easy-pdf
Easy PDF makes PDF generation in Laravel effortless: render Blade views or raw HTML through headless Chrome, then save, download, or stream the result with a clean, fluent API. Configure page size, orientation, margins, and more in just a few lines.
Install the new major explicitly. composer require tarfin-labs/easy-pdf:^3.0. Do not let a bare composer update decide for you — 3.0.0 is a feat! (breaking) release, so Composer will refuse the jump silently on older apps until you widen your own constraints.
Check the platform matrix before anything else. v3.0.0 adds Laravel 13 and PHP 8.5 support and drops legacy versions. Read the require/require-dev block in the package's composer.json to confirm your exact floor (PHP minor and the lowest supported Laravel major) — the release note names the top of the matrix, not the bottom. If you are on Laravel 10/11 or PHP < 8.3, stay on ^2.11 and plan the framework upgrade as a separate project.
Publish config first. php artisan vendor:publish --tag=easy-pdf-config (verify the tag with php artisan vendor:publish — tag names occasionally change across majors). This gives you paper size, orientation, font paths, and temp/cache directories in one place instead of scattering options through call sites.
Smoke-test with one real document. A Blade invoice rendered to a streamed download is the canonical first use case:
Route::get('/invoices/{invoice}/pdf', function (Invoice $invoice) {
return EasyPdf::loadView('pdf.invoice', ['invoice' => $invoice])
->download("invoice-{$invoice->number}.pdf");
});
Where to look first in the source: the facade/service-provider pair (to see what is bound and therefore swappable) and the config file (to see what the package actually forwards to the underlying PDF engine). Those two files answer most day-to-day questions faster than the README.
EasyPdf:: from controllers, introduce an InvoicePdf / ReportPdf class that takes a DTO and returns a response or a stored file. When 4.0 lands, or when you swap engines, you change one class instead of grepping 40 controllers.->download()/->stream() for interactive requests and a ->save($path)-style call (check the v3 signature) for queued/offline generation. Keeping both behind the same builder means the queued and synchronous outputs can't drift in layout.GenerateInvoicePdf job → write to the local/s3 disk → notify or expose a signed temporary URL. This also keeps request timeouts out of the equation on shared hosting.resources/views/pdf/…) and pass only plain arrays/DTOs into them. Anything you pass is serialized into the render context, so passing Eloquent models with lazy relations is how you get N+1s inside the renderer.pdf.layout template plus config; per-document overrides should be the exception and should be explicit at the call site.Content-Type: application/pdf, Content-Disposition filename) rather than byte-comparing output — PDFs embed timestamps and generator IDs and will never be byte-stable. For storage flows, Storage::fake('local') + assertExists() is the cheapest reliable assertion. If you need content assertions, check the extracted text with a tool, not the raw stream.composer.lock for other packages pinning the same framework majors before you start; the PDF wrapper is rarely the blocker, but it is often the package that surfaces the conflict.composer require fails with platform requirements, that error is coming from a transitive dependency, not from easy-pdf itself — check with composer why-not php 8.5.php artisan config:clear locally and ensure your deploy pipeline rebuilds the cache. Stale cached config is the single most common "my margins/paper size change did nothing" cause./tmp or a mounted path) or rendering fails with an opaque engine error.memory_limit/max_execution_time for the specific job or route, not globally. Downscale images before embedding — that is almost always cheaper than raising limits.allow_url_fopen. Many engines fetch images over HTTP themselves, bypassing Laravel's HTTP client, so timeouts, auth headers, and proxies behave differently than the rest of your app. Pre-download to a temp path or a storage disk and pass local paths.{!! !!} is an injection vector and a malformed-PDF vector. Escape by default; sanitize any intentional raw HTML.->loadView(...)->render()-style, or render the Blade view directly in the browser) and only then attempt PDF generation. Ninety percent of "broken PDF" reports are broken HTML/CSS, and PDF engines support a narrow CSS subset — no flexbox/grid in older engines, floats and tables only.composer.json ("^3.0") and add a note to your upgrade runbook: this package has shipped breaking majors before (2.x → 3.0), so treat the next one as scheduled maintenance rather than a surprise.How can I help you explore Laravel packages today?