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

Wkhtml2Pdf Laravel Package

nitmedia/wkhtml2pdf

Laravel wrapper around wkhtmltopdf for converting HTML and Blade views into PDF files. Set your binary path and options in config, then generate, stream, or download PDFs through a simple facade, with support for page size, orientation, and other wkhtmltopdf flags.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a thin facade wrapper around the wkhtmltopdf command-line binary — it does not render HTML itself. Your first job is getting the binary working, not the PHP.

  1. Install the package (it targets Laravel 4/5-era apps):
    composer require nitmedia/wkhtml2pdf
    
  2. Register it manually. The package predates Laravel 5.5 package auto-discovery, so add it to config/app.php:
    'providers' => [
        Nitmedia\Wkhtml2pdf\Wkhtml2pdfServiceProvider::class,
    ],
    'aliases' => [
        'PDF' => Nitmedia\Wkhtml2pdf\Facades\Wkhtml2pdf::class,
    ],
    
  3. Install and verify the binary first. This is the actual render engine and the source of ~90% of first-run failures:
    which wkhtmltopdf
    wkhtmltopdf --version      # confirm 0.12.x
    wkhtmltopdf https://example.com /tmp/test.pdf   # prove it works standalone
    
  4. Publish and edit the config so binpath/binfile point at the absolute path of that binary (e.g. /usr/local/bin/wkhtmltopdf). Do not assume PATH is inherited by PHP-FPM.
  5. First use case — render a Blade view to a download:
    Route::get('/invoices/{invoice}', function (Invoice $invoice) {
        return PDF::view('pdf.invoice', ['invoice' => $invoice])
            ->filename("invoice-{$invoice->id}.pdf")
            ->download();
    });
    

If step 3 doesn't work in your terminal, stop — no amount of PHP config will fix the package until the binary works.

Implementation Patterns

Builder chain with terminal output methods. Day-to-day use is a fluent chain that ends in one of three terminals:

  • ->render() — returns the raw result / response object; return it directly from a controller.
  • ->download() — sets Content-Disposition: attachment, good for "Save my invoice".
  • Inline/stream variant — good for a "Preview PDF" button.

The pattern is always source → options → filename → terminal:

return PDF::view('pdf.report', compact('rows'))
    ->filename('report.pdf')
    ->setOptions([
        'margin-top'     => 10,
        'margin-bottom'  => 10,
        'footer-center'  => 'Page [page] of [topage]',
        'footer-font-size' => 8,
    ])
    ->download();

Global vs per-request options. Put site-wide defaults (page size, margins, DPI, header/footer text, --print-media-type) in the published config so every PDF is consistent. Use setOption()/setOptions() only for the outliers — landscape statements, a one-off --orientation landscape, or --javascript-delay for a chart.

Render Blade views, not string-concatenated HTML. PDF::view('...', $data) keeps your PDF templates alongside your other Blade files, gives you @include, and makes the PDF testable with view()->render() assertions. Reserve PDF::html($string) for small snippets or when you already have rendered HTML (e.g. from a Markdown renderer).

Wrap generation in a small service class. Instead of sprinkling chains across controllers, create App\Pdf\InvoicePdf with a generate(Invoice $invoice): string that returns the saved file path. This keeps controllers thin, makes the PDF logic queueable, and means when you eventually swap the renderer (see gotchas) you change one class.

Persist to disk for anything important, stream for anything on-demand.

  • Invoices/receipts: generate once, ->filename(storage_path(...))->render(), store the path on the model, serve via a normal download route.
  • Live previews: generate on the fly and stream.
  • Batch jobs: generate in a queued job and notify by mail/event.

Queues are your friend. PDF generation is CPU-bound and blocking. A report that takes 4s synchronous will block a web worker; dispatch it to a queue and write to storage.

Reuse a Blade layout. Because it's just a view, you can share a resources/views/pdf/layout.blade.php across all documents; only the @yield content varies. Keep CSS inline or in a single <style> block — external CSS over HTTP is slow and brittle in wkhtmltopdf's old WebKit.

Gotchas and Tips

The binary path is the #1 failure. binpath in config must be an absolute directory with a trailing separator, and binfile the executable name. Under PHP-FPM PATH and even HOME may differ from your shell — always test by hitting a route, not by php artisan tinker alone (tinker inherits your shell env).

wkhtmltopdf needs headless graphics libs. On slim Docker images you'll see error while loading shared libraries: libXrender.so.1 or libfontconfig. Install libxrender1 libfontconfig1 libxext6 libssl1.1 (names vary by distro). For newer binaries, running under xvfb-run solves headless crash loops.

Relative asset URLs break. wkhtmltopdf resolves src/href against the HTML source, not your app. Either (a) use absolute URLs (asset(), url()), or (b) inject <base href="https://yourapp"> in your PDF layout. On wkhtmltopdf 0.12.6+, local file references require --enable-local-file-access or images silently vanish.

Old WebKit means old CSS. No flexbox, no CSS grid, limited border-radius/box-shadow support, no modern JS. Test your invoice layout on the binary itself before blaming the package. For JavaScript-built charts use setOption('javascript-delay', 1000) (milliseconds) and --enable-javascript.

Builder state is mutable. Chained calls mutate the same object. In long-running processes (queue workers, Octane, ReactPHP) a shared/reused instance will leak options, filename, and even the HTML from the previous call. Always build a fresh chain per document — never store a PDF instance as a singleton.

config:cache freezes your options. After editing config/wkhtml2pdf.php you must re-run php artisan config:cache (or clear it) in production, or you'll debug the wrong config for hours.

Temp directory must be writable. Large documents and headers/footers are written to a temp path before final output. Ensure storage/ (or whatever the package config points to) is writable by the PHP user — a read-only container will fail with cryptic pipe errors.

Timeouts and memory. Add a per-run timeout and raise max_execution_time for big documents. --no-stop-slow-scripts prevents early termination on JS-heavy pages, but also means it will run to the timeout — pair with a queue.

Argument injection. Options get passed to a shell command. Never interpolate user input into keys or values (e.g. a user-supplied footer-html URL). Whitelist options if they're configurable by end users.

It's a 2017 package on an unmaintained path. Practical implications:

  • No auto-discovery, no Laravel 9/10/11 guarantees — verify facade binding and return types against your Laravel version before committing.
  • wkhtmltopdf itself is archived (QtWebKit), so modern CSS and some sites increasingly render poorly.
  • Keep the renderer behind your own PdfGenerator interface. If you outgrow it, swapping to mikehaertl/phpwkhtmltopdf (same binary, better API) or spatie/browsershot / barryvdh/laravel-dompdf becomes a one-class change instead of a codebase-wide find/replace.
  • Pin the composer version and vendor the binary or a Docker image so a future composer update doesn't surprise a production deploy.

Testing tip. Bind a fake implementation of your own PdfGenerator interface in tests; assert the HTML passed to it rather than shelling out to a real binary in CI. If you must assert output, assert the file exists and is non-empty — never compare PDF bytes.

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.
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
noith/settings