danielboendergaard/phantom-pdf
Laravel wrapper for generating PDFs with PhantomJS. Render HTML strings or URLs to PDF through a simple facade, with configurable paper size, orientation, margins, headers, footers, and other PhantomJS options. Useful for invoices, reports, and printable views.
composer require danielboendergaard/phantom-pdf
php artisan vendor:publish --provider="Danielboendergaard\PhantomPdf\PhantomPdfServiceProvider"
apt install phantomjs (older Debian/Ubuntu), brew install phantomjs, npm i -g phantomjs-prebuilt, or ship it inside your Docker image. Verify with phantomjs --version before touching Laravel.config/phantom-pdf.php first. Set the absolute path to the binary (and the bundled rasterize script if it isn't auto-detected) plus a writable temp_dir. Relative paths silently fail.use PDF;
PDF::loadView('pdf.invoice', ['order' => $order])
->setPaper('a4', 'portrait')
->save(storage_path("app/invoices/{$order->id}.pdf"));
Confirm the file exists before wiring up controllers — this validates binary path, temp dir, and view rendering in one shot.Where to look when something breaks: config/phantom-pdf.php, then the package's main PhantomPdf class (to see which fluent methods actually exist), then the bundled rasterize.js (to see what CLI flags PhantomJS is being given).
PDF::loadView() goes through Laravel's view engine, so Blade layouts, @include, and @foreach work. Use PDF::loadHTML($html) when the markup comes from an editor, a mail-preview endpoint, or a third-party template store. Avoid mixing both in the same class — pick one convention per controller/service.->stream(), ->download()) is fine for small, user-triggered documents. Anything larger than a page or two, or anything batch generated, should be pushed to a queued job that calls ->save() to a disk path; PhantomJS is a synchronous subprocess and will block the request for seconds.PDF::loadView(...) through controllers. A small InvoicePdfGenerator class that owns the view name, paper settings, and storage path makes swapping the backend later (Browsershot, Dompdf) a one-file change.asset() works only if the app is reachable from the server) or inline CSS/images as data URIs. Inlining is usually more reliable than relying on the local web server.phantom, phantomjs on $PATH will work in your shell but not under php-fpm/queue workers whose PATH is minimal. Hard-code the absolute path in config and in Docker.temp_dir. The package writes intermediate files; www-data and www-data in a queue worker need write access to the same directory, or jobs fail with an unrelated-looking error.--no-sandbox or --ssl-protocol=any passed through the package's option/method escape hatch. If you see empty PDFs or immediate non-zero exits, check the raw stderr first.php artisan config:cache combined with env() values inside the config file is the classic Laravel trap — the binary path gets baked in. Use env only in deployment, and re-cache after changing it.save() returns a boolean; stream()/download() return a Laravel response. Don't try to ->save() a response and don't ->stream() inside a queued job.phantomjs rasterize.js ... command the package builds. If that succeeds, the problem is config/permissions; if it fails, it's the binary/environment — this split saves a lot of time.How can I help you explore Laravel packages today?