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

Dompdf Bundle Laravel Package

prezent/dompdf-bundle

A Symfony bundle that integrates the Dompdf library, letting you generate PDFs from HTML and CSS inside your Symfony applications with simple, bundle-style configuration and service access.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first: this is a Symfony bundle, not a Laravel package. In a Laravel app composer require prezent/dompdf-bundle will pull the code but nothing registers it — Laravel has no bundle bootstrapping, so the DI extension never runs and no service appears in the container. For Laravel, the equivalent is barryvdh/laravel-dompdf (same dompdf/dompdf engine underneath). The patterns below still apply if you end up using dompdf directly in Laravel; only the wiring differs.

Assuming a Symfony 4/5 codebase:

  1. composer require prezent/dompdf-bundle
  2. Register it if Flex doesn't: Prezent\DompdfBundle\PrezentDompdfBundle::class => ['all' => true] in config/bundles.php.
  3. Look here first — the bundle is tiny, so read all of it before guessing:
    • DependencyInjection/Configuration.php — the config tree, i.e. every option you can set.
    • Resources/config/services.xml (or services.yaml) — the service id and constructor arguments. That id is your entry point.
    • DependencyInjection/PrezentDompdfExtension.php — how config maps onto Dompdf\Options.
  4. Create config/packages/prezent_dompdf.yaml with the options you want as defaults (paper size, orientation, DPI, font dir, chroot). These are passed through to Dompdf\Options::set().
  5. First use case — controller renders a Twig template to HTML, feeds it to the service, returns a Response:
public function invoice(Invoice $invoice, Environment $twig, /* dompdf service */ $dompdf): Response
{
    $html = $twig->render('pdf/invoice.html.twig', ['invoice' => $invoice]);
    $dompdf->loadHtml($html);
    $dompdf->setPaper('A4', 'portrait');
    $dompdf->render();

    return new Response($dompdf->output(), 200, [
        'Content-Type'        => 'application/pdf',
        'Content-Disposition' => 'inline; filename="invoice-'.$invoice->getId().'.pdf"',
    ]);
}

Implementation Patterns

Wrap it, don't sprinkle it. Inject the dompdf service into one small PdfRenderer service and expose render(string $html, array $overrides = []): string. Every controller/job then depends on your class, not the bundle — which is what makes it trivial to drop the bundle later.

One document per instance. Dompdf is stateful (canvas, font metrics, loaded images, base path). If the bundle registers it as a shared service, reusing it across documents in a worker is asking for bleed-through. Either request the service fresh per render or, better, wrap a DompdfFactory-style closure that news up a Dompdf with the configured Options each time.

Template layout. Keep PDF templates in templates/pdf/, separate from web templates. Inline the CSS — either a <style> block in the template, or {% apply inline_css %} (needs twig/cssinliner-extra) if you want to share a stylesheet with the web UI. No JavaScript, no external CDN links (see gotchas).

Response strategy. Small PDFs: new Response($dompdf->output(), ...) with Content-Disposition: inline to preview, attachment to download. Large ones: write to tempnam() and return a BinaryFileResponse so you're not holding the whole string plus the response in memory.

Config layering. Put organisation-wide defaults (A4, portrait, 96 DPI, font dir) in the bundle config. Override per document with $dompdf->setPaper() / $options->set() — e.g. landscape for wide reports, A3 for schedules. Don't fork the bundle config for one-off cases.

Generate off the request cycle. Anything over a page or two, or anything batched (statements, tickets, exports), belongs in a Messenger handler: render → store via Flysystem/S3 → email or expose a signed download link. PDF generation is the classic request-timeout culprit.

Testing. Cheap and effective: assert str_starts_with($pdf, '%PDF-'), assert a plausible byte length, and assert the response Content-Type. If you need content assertions, run the output through a text extractor rather than regexing raw bytes.

Gotchas and Tips

  • Frozen since Feb 2021. Last release 2021-02-12, 2 stars, 0 dependents, no security advisory trail. Treat it as vendored code you now own. Run composer why-not prezent/dompdf-bundle before blaming your app; its Symfony/dompdf constraints will likely conflict with a current stack. If you keep it, pin dompdf/dompdf explicitly in your root composer.json and fork the bundle into src/ rather than relying on upstream fixes that aren't coming.
  • dompdf v1 vs v2. The 2021 timeframe means it probably targets dompdf 0.8/1.x. dompdf 2.x changed APIs and defaults (notably CSS handling and Options behaviour). Verify which version resolves before writing code against a tutorial.
  • Writable font cache. dompdf compiles font metrics to disk. If fontDir/fontCache point into a read-only container filesystem you get silent fallbacks to Helvetica or outright failures. Point them at %kernel.cache_dir%/dompdf (dev) or var/dompdf (prod) and make sure the directory exists and is writable at deploy time.
  • chroot silently kills images. It defaults to the dompdf install directory, so <img src="/build/logo.png"> or relative paths quietly render as nothing. Set chroot to your project root, or use absolute paths / embedded base64 data URIs for logos — base64 is the most portable option.
  • isRemoteEnabled is off by default. Remote logos and avatars disappear. Turning it on means dompdf makes outbound HTTP requests from user-influenced URLs — an SSRF vector. Enable knowingly, and never feed it untrusted src values.
  • isHtml5ParserEnabled. Needed for decent HTML5 parsing. Even with it on, there is no flexbox and no grid. Lay out with tables and floats, the way you would for a 2009 email client.
  • Page breaks. page-break-before/after work reliably. page-break-inside: avoid is unreliable on <tr> — use repeating <thead>, fixed row heights, and split long tables yourself in the template if pagination must be exact.
  • Memory and time. Wide tables blow memory_limit around the point you stop expecting it. Raise limits inside the Messenger worker or job, not globally, and consider chunking rows into multiple documents.
  • Debugging. Before render() the output is empty — don't bother calling output() early. Set logOutputFile in Options to capture dompdf's own log, set debugPng, and dump the rendered HTML from your Twig template to disk to confirm dompdf is seeing what you think it is. Nine times out of ten the bug is CSS dompdf doesn't support, not the bundle.
  • Extension points are thin. There are essentially two: the DI config tree and the service definition. To change behaviour, either set options via config or override the service definition in your app's services.yaml (or decorate it). If you find yourself needing a third extension point, that's the signal to delete the bundle and register Dompdf\Dompdf + Dompdf\Options yourself in ~20 lines of services.yaml — you lose nothing but the config tree, and you gain a dependency you can reason about.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route