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

Laravel Gotenberg Laravel Package

safermobility/laravel-gotenberg

A Laravel wrapper for Gotenberg that simplifies generating PDFs from HTML, URLs, Markdown, and Office documents. It offers a fluent, configurable API for sending content to your Gotenberg service and returning polished PDF output.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a thin Laravel wrapper around Gotenberg (via the gotenberg/gotenberg-php client) that exposes a Gotenberg facade for turning HTML, URLs, and Markdown into PDFs. You still need a running Gotenberg container — the package is the client, not the renderer.

  1. Install the package:
    composer require safermobility/laravel-gotenberg
    
  2. Publish the config and point it at your Gotenberg instance:
    php artisan vendor:publish --tag="gotenberg-config"
    
    GOTENBERG_HOST=http://gotenberg:3000
    
  3. Run Gotenberg locally (Chromium module is what you want for HTML):
    docker run --rm -p 3000:3000 gotenberg/gotenberg:8
    
  4. Sanity-check connectivity before touching application code — curl http://localhost:3000/health should return {"status":"up"}. Most "package is broken" reports are actually an unreachable container or a wrong port.
  5. First use case: render a Blade view to HTML and save it locally.
    use Safermobility\LaravelGotenberg\Facades\Gotenberg;
    
    $html = view('invoices.pdf', ['invoice' => $invoice])->render();
    
    Gotenberg::html()
        ->html($html)
        ->save(storage_path("app/invoices/{$invoice->id}.pdf"));
    

Note this release (v1.0.3) specifically fixes saving the PDF to the local filesystem. If you are starting fresh, require ^1.0.3 rather than an earlier 1.0.x — save() to a local path is the exact code path that was broken.

Implementation Patterns

Wrap generation in a service or action, not in controllers. The facade returns a builder; keeping the chain in one class makes it easy to swap paper size, margins, or the whole renderer later.

class InvoicePdf
{
    public function render(Invoice $invoice): string
    {
        $path = storage_path("app/invoices/{$invoice->id}.pdf");

        Gotenberg::html()
            ->html(view('invoices.pdf', compact('invoice'))->render())
            ->paperSize(21, 29.7)      // A4, in cm — verify units against the client
            ->margins(1, 1, 1, 1)
            ->save($path);

        return $path;
    }
}

Push it onto a queue. PDF rendering is I/O-bound and slow (hundreds of ms to several seconds). A ShouldQueue job with an explicit $timeout and $tries keeps web requests fast and gives you retries when Gotenberg is temporarily down. Write the file in the job, then dispatch a follow-up job to email or upload it.

Bridge to Laravel Storage. The package writes to a filesystem path, so the usual pattern is: render to a local temp file, then hand off to Storage. This keeps S3/GCS credentials out of the Gotenberg call entirely.

$tmp = tempnam(sys_get_temp_dir(), 'pdf_');
Gotenberg::html()->html($html)->save($tmp);
Storage::disk('s3')->put("invoices/{$id}.pdf", file_get_contents($tmp));
unlink($tmp);

Pick the right source type. HTML is best for Blade-rendered documents (invoices, statements, certificates). Markdown suits generated reports. URL rendering is best for archiving existing pages — but remember Gotenberg fetches it server-side, so internal/intranet URLs only work if the container can reach them.

Bundle your assets. CSS, fonts, and images referenced by relative URL won't resolve inside the container. Either inline everything (base64 data URIs, <style> blocks) or pass the assets explicitly so Gotenberg gets them in the same request. Base64 inlining is the most predictable approach for a handful of files.

Test against a real container. Unit-testing the facade only proves you called the builder. Put Gotenberg in your CI as a service container and assert on the resulting bytes:

services:
  gotenberg:
    image: gotenberg/gotenberg:8
    ports: ['3000:3000']

then assert the file exists and starts with %PDF-. For fast unit tests, bind a fake implementation in the container so you're not spinning up Docker for every test run.

Pin the Gotenberg image tag, not latest. The wrapper passes options through to the API, and Gotenberg 8 changed several route and option names relative to 7. An unexpected image bump is the most likely source of a sudden 400 response.

Gotchas and Tips

  • This patch matters if you save locally. v1.0.3 fixes saving the PDF to the local filesystem. If you're on 1.0.0–1.0.2 and writing to storage_path() or a temp file, upgrade — the fix is the whole point of the release. It's a patch, no breaking changes, so composer update safermobility/laravel-gotenberg is low risk.
  • The rest of the release is CI noise. The three Dependabot bumps (actions/checkout, git-auto-commit-action, fetch-metadata) are GitHub Actions versions in the package's own workflows. They have zero effect on your runtime.
  • Path permissions. storage_path('app/...') must be writable by the PHP-FPM/queue user, not just your shell user. A "fixed" save that still fails on production is almost always a permissions or missing-directory issue — create the directory first, don't assume save() does.
  • Config caching bites. After changing GOTENBERG_HOST in .env, run php artisan config:clear. Cached config in a container image is a classic silent failure.
  • Timeouts stack. You have three: the HTTP client timeout in the package config, the Gotenberg-side processing limit, and the queue worker / PHP max_execution_time. If the worker kills the job first, you get a confusing "job deleted" rather than a clear timeout. Keep the worker timeout comfortably above the HTTP timeout.
  • Remote assets are the top failure mode. Gotenberg 8 restricts outbound downloads by default. If your HTML pulls images or CSS over the network and they silently vanish from the PDF, check the container's download allow-list flags before you start debugging your HTML.
  • Fonts live in the container. Non-Latin scripts, emoji, or a specific brand font will render as boxes or fall back unless the font is installed in the Gotenberg image (or embedded via base64 @font-face). Embedding is the portable fix.
  • Headers and footers are separate HTML documents, not strings in your body HTML. They also don't apply uniformly across all Gotenberg routes — Chrome-based rendering supports them, some conversion routes don't.
  • Debug by isolating layers. Log the rendered HTML before it goes in; curl the Gotenberg health endpoint; then hit the raw route with curl and the same payload. That tells you in three steps whether the problem is your HTML, the client, or the container.
  • Extension points: the published config file is the main knob (host, timeout, HTTP client options). If you need a Gotenberg feature the facade doesn't expose, resolve the underlying gotenberg-php client from the container and call it directly rather than forking — the wrapper is thin enough that mixing the two is fine. Wrapping the facade behind your own interface also makes it trivial to swap in a different renderer later without touching call sites.
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.
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
uni-method/symfony-lib