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

leo-moshko/gotenberg-bundle

Integrates the Gotenberg document-conversion API into your PHP app, offering a simple wrapper for turning HTML, Markdown, and Office files into PDFs. Send documents over HTTP and get clean, print-ready output with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Gotenberg is a stateless Docker service that exposes an HTTP API for document conversion: HTML/Markdown/URL → PDF, Office documents → PDF, PDF merge/split/convert/flatten, and URL screenshots. This package is the PHP-side wrapper around that API.

Minimal setup

  1. Pull the package and start the service side by side — nothing works without the container:
    composer require leomoshko/gotenberg-bundle
    docker run --rm -p 3000:3000 gotenberg/gotenberg:8
    
  2. Publish/verify config and point it at the container. Expect a base URI plus timeout keys; put them in .env rather than the config file:
    GOTENBERG_URL=http://gotenberg:3000
    GOTENBERG_TIMEOUT=60
    
  3. Smoke-test with curl before touching PHP. If curl -F [email protected] http://localhost:3000/forms/chromium/convert/html -o out.pdf fails, the problem is Docker networking, not your code.

Where to look first

  • composer.json → "type" and extra. A Bundle-suffixed name usually means a Symfony bundle, i.e. no Laravel auto-discovery and possibly no extra.laravel.providers. Confirm this in the first five minutes; it changes the integration plan completely (see Gotchas).
  • src/ for the client/service class — that's the object you'll inject.
  • config/ or Resources/config/ for the endpoint map. Gotenberg 8's routes are the contract you're really coding against: /forms/chromium/convert/html, /forms/chromium/convert/url, /forms/chromium/screenshot/url, /forms/libreoffice/convert, /forms/pdfengines/{merge,convert,flatten}.

First use case

The 90% case is "render this Blade view to a PDF and store it." Get that working end to end before exploring the API surface:

$html = view('invoices.pdf', ['invoice' => $invoice])->render();

$pdf = Gotenberg::html($html)
    ->paperSize('A4')
    ->margin('10mm')
    ->pdf();

Storage::disk('s3')->put("invoices/{$invoice->id}.pdf", $pdf);

Implementation Patterns

Bind the client, don't new it. Register the package service in a service provider behind a small interface (PdfRenderer) and inject that. When the package's API shifts, or when you want to swap to raw Http:: calls, you change one binding instead of 40 call sites.

Queue the work. Chromium rendering is 200ms–10s and occasionally unbounded. Anything user-facing (invoice on checkout, report export) belongs in a job:

final class RenderInvoicePdf implements ShouldQueue
{
    public int $timeout = 120;   // must exceed the Gotenberg HTTP timeout
    public int $tries = 3;

    public function handle(PdfRenderer $renderer, Storage $storage): void { /* ... */ }
}

Dispatch, return a "generating" state to the UI, and notify on completion. This also gives you free retries for container restarts.

Multipart, not JSON. Gotenberg is form-data only. The entry document for HTML conversion must be named index.html; CSS/JS/images ride along as additional files entries with names that match the src/href paths in your markup. Inline critical CSS and base64 small images to keep the attachment list short — every extra file is a chance for a path mismatch.

Store, don't stream, for anything persisted. Use Storage::disk(...)->put() on the raw response body. For download-to-user flows, return response($pdf)->header('Content-Type', 'application/pdf'); avoid writing temp files just to hand them to response()->download().

Header/footer partials are a separate HTML document passed as its own field — they do not inherit your main document's CSS. Use inline styles only, and remember Chromium ignores @page margins inside them.

Webhooks for long jobs. Gotenberg accepts a Gotenberg-Webhook-Url header (plus method/extra headers) and will POST the result back. Pair this with a signed, api-middleware route to avoid holding a worker open during LibreOffice conversions of large decks.

Testing. Http::fake() with a fixture %PDF-1.4 body covers the wiring and your job logic cheaply. Keep one real integration test against a gotenberg/gotenberg service container in CI — that's the only place you'll catch Chromium quirks, missing fonts, and route renames.

Gotchas and Tips

"Bundle" almost certainly means Symfony, not Laravel. Check composer.json before assuming php artisan vendor:publish will do anything. If there's no extra.laravel, you have three options: wire the services manually in a provider, use the underlying HTTP client directly (Gotenberg's API is simple enough that the wrapper is optional), or add symfony/http-client just for this. Decide deliberately — don't spend an hour debugging why the container isn't resolved.

Error bodies are plain text, not JSON. On a 4xx/5xx, Gotenberg returns a human-readable string. ->json() on the failure path throws a confusing JsonException and hides the actual message ("Chromium failed to load the page", "invalid paper size"). Always read ->body() on error and log it.

Timeouts stack. Browser timeout (Gotenberg), GOTENBERG_TIMEOUT (HTTP client), $timeout (queue worker), and PHP max_execution_time all apply. Any one of them being the smallest produces a truncated failure that looks like a Gotenberg bug. Set the HTTP timeout below the worker timeout, always.

The container has its own limits. Chromium needs more memory than you think; --api-timeout, --chromium-restart-after, and --libreoffice-restart-after flags matter under load. A single-worker Gotenberg will serialize requests — scale with chromium-... auto-restart or run multiple containers behind a queue.

Fonts. The image ships with a limited font set. Cyrillic, CJK, and emoji glyphs render as tofu unless you mount a font volume or attach the font files with your request. Verify with real production content, not Lorem ipsum.

External assets need network access from inside the container. https://cdn.tailwindcss.com in a Blade layout will silently fail (or hang until timeout) if the container is network-isolated. Inline the CSS for PDF layouts; treat PDF templates as a separate, self-contained design rather than a reuse of your web layout.

index.html naming is non-negotiable for HTML conversion, and relative asset paths must resolve against the flat set of attached files. Nested paths are a common source of blank pages with no error.

Gotenberg has no authentication. Never expose port 3000 publicly, and be very careful with URL-conversion endpoints — you're handing an internal service the ability to fetch arbitrary URLs (SSRF). Validate or allowlist hosts if users influence the target.

Pin the image tag. Gotenberg 7 → 8 renamed LibreOffice routes and changed flags. An unpinned :latest will break your deploy on someone else's schedule. Match the image tag to whatever the package's README targets.

Debugging playbook: send Gotenberg-Trace: <uuid>, read the trace header on the response, then docker logs the container filtering by that ID. Nine times out of ten it tells you exactly which asset or expression failed. For a fast bisect, curl the same multipart payload from the host — if curl works and PHP doesn't, it's your client; if curl fails too, it's the document.

On v1.1.0: the release notes are only a changelog link, so read the commits before upgrading. A minor bump on a young package typically means new fluent options or config keys — the risky part is config reshuffles that silently fall back to defaults (paper size, timeouts) rather than throwing. Diff your published config against the package's after upgrading, and re-run your one real integration test.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment