contoweb/laravel-pdflib
A Laravel wrapper for PDFlib that brings the PDFlib API into your Laravel apps for generating and manipulating PDF documents. Create, edit, and render PDFs with a familiar facade-style interface and framework-friendly configuration.
contoweb/laravel-pdflib is a thin Laravel wrapper around PDFlib (PDFlib GmbH's commercial PDF engine), not an HTML-to-PDF renderer. If your mental model is "Blade template in, PDF out", stop now and reach for Browsershot/dompdf instead. This package is the right tool when you need precise, coordinate-level control: stamping data onto an existing PDF, generating print-ready documents with exact typography, embedding fonts, barcodes, or PDF/A output.
Minimum setup:
composer require contoweb/laravel-pdflibphp artisan vendor:publish --tag=pdflib (verify the tag in the README; this package's config holds at minimum your license key and often font/template paths)..env, never in the committed config.Look first at, in order: the published config/pdflib.php, the facade's public surface (it exposes the wrapped PDFlib instance — that instance's method names are the upstream PDFlib API), then the PDFlib API reference itself. The package README will be short; the real documentation lives on PDFlib's site.
First use case that earns its keep: stamping dynamic data onto a designer-supplied PDF template (invoice, certificate, contract). Import the template page, place it with fit_pdi_page, then draw variable text on top. That is where PDFlib beats HTML renderers on fidelity, and it's the workflow most teams adopt first.
Inject the wrapper, don't call the facade in domain code. Resolve the PDFlib wrapper through the container in a dedicated InvoicePdfGenerator / action class. Facades are convenient in a controller but make the generation logic untestable and hide the license/extension dependency from readers.
One document per instance, one instance per document. PDFlib objects carry significant internal state (open documents, loaded fonts, page buffers). Treat the instance as request-scoped, not as a long-lived singleton. If the package binds it as a singleton, override that binding to scoped in your own service provider — see gotchas below.
Template + overlay is the dominant pattern. Compose it as: open_pdi_document on the template → begin_page_ext → fit_pdi_page (place the template page) → load_font → measure with info_textline → show_textline/show_text at computed coordinates → end_page_ext → end_document. Measure before you place, or you will ship overlapping text.
Keep generation out of the request cycle. Generate in a queued job, write to Storage::disk('local') via storage_path(), and return a signed/expiring URL or attach the bytes to a mail. PDFlib is fast but synchronous and memory-hungry; a 200-page document in an FPM worker is a timeout waiting to happen.
Output routing. get_buffer() for in-memory responses (stream with response()->streamDownload() and set Content-Type: application/pdf), or write directly to a path for archival. Never echo PDFlib output — any stray whitespace from a Blade/config file corrupts the byte stream.
Config layering for multi-tenancy. If tenants bring their own license keys or brand fonts, resolve those from your own config/DB layer and pass them into the generator rather than relying on the single published config file.
Testing. You cannot meaningfully assert on PDF bytes. Assert on the inputs to the generator (the data object), and use a fake/smoke test that asserts a non-zero-length, %PDF- prefixed output. Keep a small golden-file test with a checksum only if the PDFlib version is pinned.
php artisan config:clear after any .env change).0/false and an error string rather than throwing. Check get_errmsg() when a call fails; the wrapper may or may not convert these to exceptions — confirm before you rely on try/catch.A4 is 595×842pt. Porting values from a CSS/HTML designer's mockup requires flipping the Y axis and converting px→pt (×0.75).unicode option on load_font (and a Unicode-capable font). Missing glyphs render as blanks or boxes, not errors.scoped or explicitly begin/end documents in a finally block.memory_limit in the queue worker, not globally.composer update; just verify the illuminate/* constraint in composer.json matches your framework version and re-run your generation smoke test against the same PDFlib extension build.How can I help you explore Laravel packages today?