rekalogika/gotenberg-pdf-bundle
A Symfony bundle that integrates Gotenberg, the Docker-powered PDF generation service, into your PHP app. Generate PDFs from HTML, URLs, or Markdown through a simple service or Twig, with configurable hosts, timeouts, and assets. Simplifies PDF creation without managing headless browsers yourself.
First, the elephant in the room: this is a Symfony bundle, not a Laravel package. If you're a Laravel developer, you cannot composer require this and expect it to work — there's no ServiceProvider, no facade, no config publishing in Laravel's style. You have two honest options before going further:
rekalogika/gotenberg-pdf. For Laravel, use that library directly, or use a Laravel-native alternative (e.g. spatie/laravel-pdf when it targets Gotenberg, or a direct Guzzle HTTP client against Gotenberg).Assuming you're in a Symfony context, minimal steps:
Run Gotenberg (it's a Docker sidecar, not a PHP library):
docker run --rm -p 3000:3000 gotenberg/gotenberg:8
Verify with curl http://localhost:3000/health.
Require the bundle:
composer require rekalogika/gotenberg-pdf-bundle
Register it in config/bundles.php:
Rekalogika\GotenbergPdfBundle\RekalogikaGotenbergPdfBundle::class => ['all' => true],
Configure the Gotenberg endpoint in config/packages/rekalogika_gotenberg_pdf.yaml (verify the exact key against config/ in the repo — the vendor tree is authoritative):
rekalogika_gotenberg_pdf:
http_client: 'http://gotenberg:3000'
Where to look first: read vendor/rekalogika/gotenberg-pdf-bundle/config/ for the service definitions and the src/ for the injected service interface. The public service you inject is your entry point (typically a GotenbergPdfInterface or a *Generator service — read the bundle's services.php to confirm the exact ID/alias).
First use case: convert an HTML string or URL to a PDF and stream it to the browser.
public function download(PdfGeneratorInterface $pdf): Response
{
$result = $pdf->generateFromHtml('<h1>Hello</h1>');
return new Response(
$result->getStream(), // or getContent() depending on API
200,
['Content-Type' => 'application/pdf']
);
}
Inject, don't instantiate. The value of the bundle is the DI wiring — the HTTP client (PSR-18), the Gotenberg base URI, and any options are configured once and injected. Never hand-roll a Guzzle client against Gotenberg inside your controller; you'll lose timeouts, retries, and config.
HTML → PDF is the primary path. Typical call center is:
$html = $this->renderView('pdf/invoice.html.twig', ['invoice' => $invoice]);
$stream = $pdf->generateFromHtml($html, /* options */);
Options are passed per-call but configured for defaults. Gotenberg itself accepts a rich set of options (paper size, margins, header/footer HTML, wait-for-selector, print background, scale, etc.). The bundle exposes these — check the options DTO/array shape in src/. The pattern to adopt:
Treat Gotenberg as an external service for observability. Wrap calls in your application's error handling. Gotenberg failures are HTTP failures — a 400 means your HTML/options are bad, a 5xx/timeout means the container is struggling. Log the response body on failure; Gotenberg returns useful diagnostics.
Streaming vs. buffering. For large PDFs, prefer streaming to avoid loading the whole document in PHP memory. The underlying HTTP response is a stream — decide early whether you pipe it straight to the browser, store it via Flysystem, or return it. Don't buffer if you can stream.
Common integration shape for an app:
PdfRenderer application service wrapping the bundle's service.This is Symfony, not Laravel — the biggest gotcha. No config/pdf.php publishing, no facade, no container auto-discovery conventions Laravel devs assume. If your goal is a Laravel app, evaluate rekalogika/gotenberg-pdf (the library) or a Laravel package directly; don't fight the bundle.
Gotenberg must be reachable from PHP, not from the browser. In Docker Compose this means two containers with the service name as host (http://gotenberg:3000), not localhost — localhost inside the PHP container is the PHP container. This is the single most common "it hangs / connection refused" cause. Health-check Gotenberg before debugging your code.
Version pinning matters. Gotenberg's own API changes between major versions (v7 vs v8), and the library targets specific versions. Pin the Docker image tag (gotenberg/gotenberg:8) to match what the library expects, and pin the library version in Composer. Silent API drift will surface as 400s with cryptic messages.
Low-star, early-stage project — verify the API before you build on it. With ~11 stars, expect naming churn. Read src/ and config/ in vendor/ rather than trusting docs or blog posts. Lock the Composer version and treat upgrades as deliberate events with a test suite around PDF generation.
Long conversions block the worker. Gotenberg renders synchronously from PHP's perspective. Complex documents (large images, external assets, JS-heavy HTML) can take seconds. Push generation to Messenger/queue for anything user-facing that isn't instant-feedback.
External assets fail silently by default. If your HTML references images/CSS by URL, Gotenberg must be able to fetch them. Gotenberg has options like --gotenberg-download-from / allowlist configuration that must permit your hosts. A PDF that "renders" but is blank-styled is almost always this — check the Gotenberg container's outbound access and its downloadFrom config.
Timeouts are two-sided. Set both the HTTP client timeout (Symfony framework.http_client) and Gotenberg's own --api-timeout. A PHP timeout shorter than Gotenberg's means you abandon a render that's still running, wasting container resources.
Fonts. Non-Latin scripts and custom fonts require them to be present in the Gotenberg container or available via @font-face URLs it can fetch. This is an infrastructure concern, not a bundle one, but it bites every project eventually — build a custom Gotenberg image with fonts baked in.
Debugging strategy, in order: curl the Gotenberg /health; curl a minimal /forms/chromium/convert/html request directly against the container with your HTML; only then debug the PHP layer. If raw Gotenberg works but the bundle doesn't, the problem is options or the injected base URI — dump the resolved config from the container (bin/console debug:container, bin/console config:dump rekalogika_gotenberg_pdf).
Extension points to know: the bundle wires a PSR-18 HTTP client — you can swap it (e.g. for one with retries/logging) via service decoration or override. Options are typically a value object/array you can construct in a custom service; if you need per-tenant behaviour, decorate the generator rather than editing vendor config.
How can I help you explore Laravel packages today?