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

Php Weasyprint Laravel Package

pontedilana/php-weasyprint

PhpWeasyPrint is a PHP wrapper for WeasyPrint that generates PDFs from URLs or HTML. Inspired by Snappy and built as a drop-in replacement, it offers a strictly typed GeneratorInterface and requires PHP 8.3+ plus WeasyPrint 60 or greater.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install the library and the binary — they are two separate things.

composer require pontedilana/php-weasyprint

The package is only a wrapper; the actual renderer is the Python CLI. Your image/VM needs WeasyPrint 60+ (the current release supports up to WeasyPrint 68 options):

# Dockerfile — Debian/Ubuntu flavour
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3 python3-pip python3-venv \
    libpango-1.0-0 libpangoft2-1.0-0 libcairo2 libgdk-pixbuf-2.0-0 \
    shared-mime-info fonts-dejavu-core fonts-noto-core \
 && pip3 install --break-system-packages "weasyprint>=60" \
 && weasyprint --version

Verify from inside the app container, as the same user PHP runs as: weasyprint --version. If that fails, nothing below will work.

2. Wire it into the container once. Put the binary path in config, not in controllers:

// config/weasyprint.php
return [
    'binary'  => env('WEASYPRINT_BINARY', '/usr/local/bin/weasyprint'),
    'timeout' => (int) env('WEASYPRINT_TIMEOUT', 60),
];
// app/Providers/AppServiceProvider.php
use Pontedilana\PhpWeasyPrint\Pdf;

public function register(): void
{
    $this->app->bind(Pdf::class, fn () => tap(
        new Pdf(config('weasyprint.binary')),
        fn (Pdf $pdf) => $pdf->setTimeout(config('weasyprint.timeout')),
    ));
}

Bind as transient (bind, not singleton) — see the state-leak gotcha below.

3. First use case: invoice/report from a Blade view.

public function show(Invoice $invoice, Pdf $pdf)
{
    $html = view('pdf.invoice', ['invoice' => $invoice])->render();

    return response()->streamDownload(
        fn () => print $pdf->getOutputFromHtml($html),
        "invoice-{$invoice->number}.pdf",
        ['Content-Type' => 'application/pdf'],
    );
}

Where to look first: GeneratorInterface (it mirrors KnpSnappy's contract: getOutput, getOutputFromHtml, getOutputFromUrl, generate, generateFromHtml, generateFromUrl), then Pdf::setOption() cross-referenced against the output of weasyprint --help on your installed version. The Pontedilana\PhpWeasyPrint\Enum\* classes only cover a handful of constrained options — everything else is a raw CLI flag name.

Implementation Patterns

Treat HTML generation and PDF rendering as two steps. Build the HTML in a Blade view (or a dedicated DocumentBuilder class), then hand a plain string to the generator. This keeps the PDF layer testable — you can assert on the HTML without the binary installed.

Store, don't stream, for anything generated in the background. generateFromHtml() writes straight to a path and avoids holding the whole document in PHP memory:

final class StatementService
{
    public function __construct(private Pdf $pdf) {}

    public function build(Statement $statement): string
    {
        $path = "statements/{$statement->id}.pdf";
        $html = view('pdf.statement', compact('statement'))->render();

        $this->pdf->generateFromHtml($html, Storage::path($path));

        return $path;
    }
}

Queue heavy documents. Rendering is a subprocess with real latency (hundreds of ms to several seconds). A ShouldQueue job keeps the request cycle clean and lets you apply queue-level retries and timeouts instead of the library's:

final class GenerateInvoicePdf implements ShouldQueue
{
    public $timeout = 120;
    public $tries = 3;

    public function handle(Pdf $pdf): void
    {
        $pdf->disableTimeout(); // let the queue worker own the timeout
        $pdf->generateFromHtml($html, $outputPath);
    }
}

Attach to mail directly from the string. This is one of the highest-value day-to-day uses:

Mail::to($invoice->customer)->send(new InvoiceMail(
    $invoice,
    $pdf->getOutputFromHtml(view('pdf.invoice', compact('invoice'))->render()),
));

// InvoiceMail::build()
->attachData($this->pdfBytes, "invoice-{$this->invoice->number}.pdf", ['mime' => 'application/pdf'])

Use base-url (or <base href>) for any HTML passed as a string. Relative src/href paths in a string have no document URL to resolve against. Either inline assets as data: URIs, pass absolute paths, or:

$pdf->setOption('base-url', config('app.url'));

Keep a dedicated print stylesheet. Don't reuse your Bootstrap web layout. WeasyPrint has excellent paged-media support, so lean on it:

@page {
    size: A4;
    margin: 20mm 15mm;

    @bottom-center {
        content: "Page " counter(page) " of " counter(pages);
        font-size: 9pt;
    }
}

thead { display: table-header-group; }  /* repeat headers across pages */
tr    { break-inside: avoid; }

Preset options per document type rather than sprinkling setOption calls. Use the enums where they exist:

use Pontedilana\PhpWeasyPrint\Enum\PdfVariant;

// Archival invoices (PDF/A)
$pdf->setOption('pdf-variant', PdfVariant::PdfA3b);
$pdf->setOption('pdf-version', '1.7');
$pdf->setOption('attachment-relationship', 'Data');

// Fast internal reports
$pdf->setOption('optimize-images', true);
$pdf->setOption('quiet', true);

Testing. Because the renderer is an external process, split your tests: unit-test the Blade/HTML output normally, and bind a fake Pdf for feature tests that only need to assert a download was offered. Keep a small number of integration tests that actually invoke the binary, tagged with a group and skipped when WEASYPRINT_BINARY is absent in CI.

Gotchas and Tips

The 10-second timeout bites first. Both the Symfony Process timeout and a --timeout CLI flag default to 10s. Large tables, remote images, or a cold Python start will blow through it. Raise it per environment — and note that setTimeout() controls both, while setOption('timeout', null) disables only the WeasyPrint-side flag.

The Pdf instance is mutable state. setOption() mutates the object. If you register it as a singleton, options set in one request leak into the next — an invoice quietly rendered as PDF/A because a previous job asked for it. Bind transient, or wrap in try/finally { $pdf->resetOptions(); }.

Only one input source per render. Unlike Snappy, you cannot pass an array of HTML strings/URLs to merge. If you're migrating from Snappy and rely on merging, restructure into a single HTML document with concatenated sections. Also: image generation (Pontedilana\PhpWeasyPrint\Image) is gone — WeasyPrint 53+ doesn't emit images.

Missing fonts produce silent tofu. WeasyPrint uses fontconfig. fonts-dejavu-core and fonts-noto-core in the image handle 95% of cases; for brand fonts, ship the .ttf/.woff2 into the image and reference it with @font-face using an absolute file:// or storage path plus a correct base-url.

getOutput() returns the entire PDF as a PHP string. For a 300-page statement that's tens of megabytes in memory, plus the subprocess buffer. Prefer generateFromHtml() to a file and serve with response()->file(), or streamDownload() with a callback.

Security defaults are good — keep them. Commands are executed from an argument array, never through a shell, so option values and paths can't inject. Option URLs (attachment, stylesheet) are fetched server-side only for http/https unless you opt in via the fourth constructor argument. If end users supply the HTML or URLs, also constrain WeasyPrint itself:

$pdf->setOption('allowed-protocols', 'http,https,data');
$pdf->setOption('no-http-redirects', true);
$pdf->setOption('fail-on-http-errors', true); // surfaces broken asset URLs as failures

fail-on-http-errors is a double-edged sword — one flaky CDN image turns into a failed invoice. Enable it in staging, not necessarily in production.

Version drift between the library and the binary is the #1 confusing failure. Passing an option the installed WeasyPrint doesn't know yields an "unrecognized arguments" process failure. Pin both: the library version and the WeasyPrint version in your image. Bump them together and re-run the integration suite.

Debugging failing renders. Log $pdf->getCommand() (human-readable, shell-escaped — for logs only, not for execution) and the process stderr, which the library surfaces through the exception message. Then reproduce outside PHP:

weasyprint --base-url=https://app.test --verbose input.html /tmp/out.pdf

Adding setOption('verbose', true) or setOption('debug', true) to the library gets you the same trace in the exception.

Customising beyond options. The extension point is subclassing Pdf/AbstractGenerator. Be aware of the 2.x signature change: executeCommand() now takes array $command, not string, and the binary executability check moved to checkBinary(). If you override these, mirror the array-based contract — never re-introduce a shell string.

Temp files. String input is written to a temp file before rendering. The 2.6.0 release tightened cleanup so only files inside the temp directory are deleted at shutdown — if you set a custom temp dir, make sure it's writable and on a volume with space; long-running queue workers can otherwise accumulate stale PDFs.

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.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc