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

Gotenberg Bundle Laravel Package

sensiolabs/gotenberg-bundle

Symfony bundle that generates, streams, and saves PDFs and screenshots from URLs, HTML, Markdown, or Office files via the Gotenberg API. Includes a profiler and testing helpers.

View on GitHub
Deep Wiki
Context7

Getting Started

Start with the honest framing: sensiolabs/gotenberg-bundle is a Symfony bundle, not a Laravel package. There is no ServiceProvider, no facade, no package discovery. Running composer require sensiolabs/gotenberg-bundle inside a Laravel app installs files and gives you nothing callable. Every day-to-day decision flows from how you bridge that gap.

Before any code, one hard prerequisite that has nothing to do with Laravel: a running Gotenberg 8.x server.

docker run --rm -p 3000:3000 gotenberg/gotenberg:8

That container is the actual product; the bundle is a typed, fluent PHP wrapper over its HTTP API. Budget your first hour for the container, not the code.

Three integration paths, pick one deliberately:

  1. Sidecar (recommended for most teams). A tiny Symfony app using this bundle, exposed as one internal HTTP endpoint (POST /render/pdf). Laravel calls it with Http::post(). You get the bundle's builders, header/footer handling, asset inlining, and webhook support without dragging Symfony into your app.
  2. Skip the bundle, keep the ideas. composer require gotenberg/gotenberg-php in Laravel directly. Then read the bundle's docs as a specification for options and edge cases — they're better than the raw client's.
  3. Full bridge. Install symfony/http-client, symfony/routing, symfony/twig-bundle and wire the builders manually. Possible; only worth it if you're already running Symfony components.

Where to look first: docs/pdf/HtmlPdfBuilder.md, docs/assets.md, docs/processing.md, and the FAQ on blank PDFs. The builder API docs under docs/pdf/builders_api are auto-generated (php ./docs/generate.php), so they stay accurate against the code.

First use case: invoice/report PDF from HTML you already render. In Laravel that's view('pdf.invoice', $data)->render() piped into the HTML builder — the raw-HTML-string support added in 1.3.0 maps cleanly onto Blade output, and means you never touch Twig.

Implementation Patterns

Terminal calls and processing. Builders are fluent and end in ->generate(), then either ->stream() or a save/processing step. In a Laravel app never stream() from a queue worker — save to a path and hand off to Storage::putFileAs(). Streaming bytes through a job just to write them again is wasted memory.

Queue everything. Chromium renders and LibreOffice conversions take seconds and block a PHP-FPM worker. The unit of work is a job: render view → save() to a temp path → Storage::disk()->put() → dispatch a PdfGenerated event → return a signed URL. This is the single most important pattern; everything else is decoration.

Assets. The bundle ships gotenberg_asset(), which both rewrites the URL and registers the file as a multipart part sent alongside the HTML. In Laravel you have two equivalents: inline small images as data: URIs from public_path(), or use the builder's asset-adding method with paths resolved from storage_path()/public_path() and a small gotenberg_asset()-shaped helper. Relative asset paths inside the HTML will silently 404 — Gotenberg fetches nothing from your app unless you tell it to.

Routes vs. URLs. ->url('https://...') and ->route('invoice.show', [...]) both make Gotenberg fetch your app. That requires request_context.base_uri to be resolvable from inside the Gotenberg container — http://host.docker.internal:8000, not localhost:8000. If you go the sidecar route, put that config in the sidecar and keep it out of Laravel entirely.

Header/footer as full documents. Header and footer builders each take their own template, and each must be a complete <!DOCTYPE html><html><head>…</head><body>…</body></html> document. A Blade partial with just a <div> renders blank. Keep one layout file per header/footer, share the CSS, and don't try to be clever with includes.

Async + webhooks. Gotenberg accepts a per-request webhook URL; the bundle documents the wiring in docs/webhook.md. In Laravel, register the receiver outside the web middleware group (no CSRF token), verify a shared secret you embed in the URL, and key the stored file by trace ID. This is the right pattern for large batches where you don't want a worker blocked for 60s.

Merge is the builder you'll actually use most. Merge, split, flatten, encrypt, and embed are separate entry points on the PDF interface. Combining generated statements into a single packet, then flattening or encrypting it, is a three-builder chain — worth encapsulating in one application service so controllers never touch builders directly.

If you build the bridge: bind HttpClientInterface to HttpClient::create(['base_uri' => config('gotenberg.dsn')]), Twig\Environment to a FilesystemLoader, and a UrlGeneratorInterface adapter over Laravel's route(). The builder internals are plain PHP beyond those three collaborators.

Gotchas and Tips

Blank PDFs with a 200 response. This is the number one issue by volume and it has exactly two causes: self-signed TLS (add --chromium-ignore-certificate-errors to the Gotenberg command) or unreachable URLs from the container's point of view. Both are in the FAQ; both will still happen to you. Write them into your own README before someone else hits them.

Version lockstep. The bundle tracks Gotenberg server field additions release-by-release (8.28 brought bookmarks, autoIndexBookmarks, downloadFrom; 8.29 brought skipNetworkAlmostIdleEvent and the webhook events header). If you call a builder method added for 8.29 against a pinned gotenberg:8.20 image, you get silent no-ops, not errors. Pin the image tag and treat bundle upgrades as server upgrades.

Timeouts. Laravel's HTTP client defaults to 30 seconds. Heavy Chromium renders and large Office conversions exceed that. Set an explicit per-request timeout, and raise Gotenberg's own --api-timeout alongside it — the failure mode when they mismatch is a truncated file, not an exception.

No profiler, no test helpers. The profiler panel and the PHPUnit mock helpers are Symfony-only. Replace them with Http::fake() using a recorded Gotenberg response body, and assert on the builder's outbound payload shape rather than on the PDF. For debugging real failures, log the Gotenberg-Trace response header alongside your job ID — it's the only reliable correlation between a bad PDF in storage and a request in the container logs.

Don't half-boot the Symfony container. The configuration surface (sensiolabs_gotenberg.http_client, assets_directory, request_context) is YAML-first and designed for Flex. Replicating it in config/gotenberg.php with a bespoke translation layer is maintenance debt with no upside. If you need the bundle's behaviour, run it as a sidecar; if you need it in-process, use the underlying PHP client.

Extension points, if you do port it. Builder payload normalization is discoverable via NormalizeGotenbergPayload methods walked up the class hierarchy (fixed in 1.3.0). That's the sanctioned seam for adding domain-specific form fields — use it rather than forking builders. Note declare_strict_types is deliberately off in the project's cs-fixer config, which tells you something about the tolerance for defensive typing in custom payloads.

Ecosystem expectations. MIT, SensioLabs-maintained, 1.x with proper UPGRADE-1.x.md guides and solid docs — but roughly 240 stars and zero dependents. There is no StackOverflow corpus. Plan on reading source when something misbehaves; the source is small and readable, so this is a fine trade, but don't schedule around community answers.

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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel