mavorion/laravel-pdf-legacy

Create PDFs in Laravel apps

Maintainers

Package info

github.com/MavorionDeveloper/laravel-pdf

Homepage

pkg:composer/mavorion/laravel-pdf-legacy

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

1.0.0 2026-07-31 09:07 UTC

This package is auto-updated.

Last update: 2026-07-31 09:14:03 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

This package provides a simple way to create PDFs in Laravel apps. It supports multiple drivers: Browsershot (Chromium), Gotenberg (Docker-based), Cloudflare Browser Run, WeasyPrint (Python-based), DOMPDF (pure PHP), and chrome-php/chrome (Chromium). You can use modern CSS features like grid and flexbox with the Chromium-based drivers, use WeasyPrint for excellent CSS Paged Media support, or choose DOMPDF for a zero-dependency setup.

legacy-php71 branch note: this branch is a manual backport targeting PHP 7.1 / Laravel 5.2-5.5, for projects that can't run the PHP 8.2+ main branch. It ships only the Browsershot driver (spatie/browsershot pinned to ^3.26, the newest release whose symfony/process requirement still overlaps with Laravel 5.5's). Dropped compared to main: every other driver, PDF encryption/password-protection (->encrypt()), ->scale(), ->tagged(), AWS Lambda (->onLambda()), and non-mm units for ->margins()/->paperSize() - all of those throw CouldNotGeneratePdf if called, since either the dependency has no PHP 7.1-compatible release or this old Browsershot version doesn't support the option. See config/laravel-pdf.php and src/Exceptions/CouldNotGeneratePdf.php for specifics. This branch is not maintained in lockstep with main.

PdfBuilder doesn't implement Illuminate\Contracts\Support\Responsable (that interface doesn't exist before Laravel 5.5, and this branch supports 5.2+) - so return Pdf::view(...)->download(); from a controller won't auto-convert to a response. Call ->toResponse(request()) explicitly instead (see the example below). ->saveQueued() needs Illuminate\Foundation\Bus\PendingDispatch, which doesn't exist before Laravel 5.3 either - queued generation isn't available on 5.2.

Here's a quick example:

use Mavorion\LaravelPdf\Facades\Pdf;

Pdf::view('pdfs.invoice', ['invoice' => $invoice])
    ->format('a4')
    ->save('invoice.pdf')

This will render the Blade view pdfs.invoice with the given data and save it as a PDF file.

You can also return the PDF as a response from your controller:

use Mavorion\LaravelPdf\Facades\Pdf;

class DownloadInvoiceController
{
    public function __invoke(Invoice $invoice)
    {
        return Pdf::view('pdfs.invoice', ['invoice' => $invoice])
            ->format('a4')
            ->name('your-invoice.pdf')
            ->toResponse(request());
    }
}

You can use also test your PDFs:

use Mavorion\LaravelPdf\Facades\Pdf;

it('can render an invoice', function () {
    Pdf::fake();

    $invoice = Invoice::factory()->create();

    $this->get(route('download-invoice', $invoice))
        ->assertOk();

    Pdf::assertRespondedWithPdf(function (PdfBuilder $pdf) {
        return $pdf->contains('test');
    });
});

Laravel Boost

This package ships with a Laravel Boost skill. After installing the package, run php artisan boost:install to register the skill. This will help AI agents in your project generate correct PDF code.

Support us

We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.

Documentation

All documentation is available on our documentation site.

Testing

For running the testsuite, you'll need to have Puppeteer installed. Pleaser refer to the Browsershot requirements here. Usually npm -g i puppeteer will do the trick.

Additionally, you'll need the pdftotext CLI which is part of the poppler-utils package. More info can be found in in the spatie/pdf-to-text readme. Usually brew install poppler-utils will suffice.

Finally run the tests with:

composer test

Production deployment (legacy-php71 branch)

Browsershot doesn't render PDFs itself - it shells out to Node, which drives a real Chrome/Chromium. Both have to be present on whatever host actually runs PHP-FPM, not just wherever composer install was run.

Requirements

Requirement Version Notes
PHP 7.1 With ext-json. EOL upstream - see Security below.
Node.js >= 10.18.1 Anything older can't run the puppeteer version this branch needs.
puppeteer exactly 13.7.0, installed globally via npm See the warning below - do not take latest.
Chrome or Chromium any current release Referenced by path in config, not downloaded by puppeteer.
System libraries ~20 packages NSS, GTK, X11, ALSA - see the install commands below.
spatie/browsershot ^3.26 Already pinned in this branch's composer.json.

Pin puppeteer to 13.7.0 - do not install latest. Puppeteer 22+ changed page.pdf() and page.screenshot() to return a Uint8Array instead of a Node Buffer. Browsershot's bundled browser.js driver script calls .toString('base64') on the result - on a Buffer that produces valid base64; on a Uint8Array it silently returns a comma-joined list of decimal byte values instead, with no error thrown. Anything that reads PDF content back into PHP (->base64(), ->meta()) ends up corrupted. File-based ->save($path) is unaffected, since Chrome writes that file directly regardless of the bug.

Install (Docker)

Recommended: bake everything into the image so it's self-contained.

FROM php:7.1-fpm

# --- Node.js LTS ---
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
RUN apt-get install -y nodejs

# --- Google Chrome + everything headless Chrome links against ---
RUN curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add -
RUN echo "deb https://dl.google.com/linux/chrome/deb/ stable main" \
      > /etc/apt/sources.list.d/google-chrome.list
RUN apt-get update && apt-get install -y \
      google-chrome-stable fonts-liberation libasound2 \
      libatk-bridge2.0-0 libatk1.0-0 libcairo2 libcups2 libdbus-1-3 \
      libdrm2 libexpat1 libgbm1 libglib2.0-0 libgtk-3-0 libnspr4 \
      libnss3 libpango-1.0-0 libx11-6 libxcomposite1 libxdamage1 \
      libxext6 libxfixes3 libxkbcommon0 libxrandr2 xdg-utils \
    && rm -rf /var/lib/apt/lists/*

# --- puppeteer, pinned, using system Chrome instead of its own download ---
ENV PUPPETEER_SKIP_DOWNLOAD=true
RUN npm install -g puppeteer@13.7.0

# run as a real non-root user so Chrome's own sandbox can initialize
RUN useradd -m -u 1000 app
USER app

Install (bare metal / VM)

Same dependencies, installed on the host directly. PHP 7.1 no longer ships in current Ubuntu/Debian repos, so it comes from a third-party PPA.

# PHP 7.1 (EOL upstream, needs a maintained third-party repo)
sudo add-apt-repository ppa:ondrej/php   # Ubuntu; use packages.sury.org on Debian
sudo apt-get update
sudo apt-get install -y php7.1-fpm php7.1-cli php7.1-mbstring php7.1-xml php7.1-curl php7.1-zip php7.1-sqlite3

# Node.js LTS
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
sudo apt-get install -y nodejs

# Google Chrome + its shared-library dependencies
curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
echo "deb https://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
sudo apt-get update && sudo apt-get install -y google-chrome-stable \
  fonts-liberation libasound2 libatk-bridge2.0-0 libatk1.0-0 libcairo2 \
  libcups2 libdbus-1-3 libdrm2 libexpat1 libgbm1 libglib2.0-0 libgtk-3-0 \
  libnspr4 libnss3 libpango-1.0-0 libx11-6 libxcomposite1 libxdamage1 \
  libxext6 libxfixes3 libxkbcommon0 libxrandr2 xdg-utils

# puppeteer, pinned, no bundled Chromium download
sudo PUPPETEER_SKIP_DOWNLOAD=true npm install -g puppeteer@13.7.0

If Chrome fails with error while loading shared libraries: lib<name>.so: cannot open shared object file, that library is still missing - run ldd $(which google-chrome-stable) to see exactly what's unresolved. Package names differ on RHEL/Alma/Fedora (nss, atk, at-spi2-atk, cups-libs, libXcomposite, etc.).

Using nvm instead of a system-wide Node install

If Node is managed per-user via nvm (common when a server already runs an older Node for other apps), install a second version rather than upgrading the existing one:

nvm install 20   # any LTS >= 10.18.1
nvm use 20
PUPPETEER_SKIP_DOWNLOAD=true npm install -g puppeteer@13.7.0

Then point the config at the resolved absolute paths (nvm which 20 / npm root -g), not at nvm/node on PATH:

LARAVEL_PDF_NODE_BINARY=/home/<user>/.nvm/versions/node/v20.20.2/bin/node
LARAVEL_PDF_NODE_MODULES_PATH=/home/<user>/.nvm/versions/node/v20.20.2/lib/node_modules

This matters because PHP-FPM's non-interactive process never sources nvm.sh - node/npm won't be on its PATH and the nvm shell function won't exist, so a bare LARAVEL_PDF_NODE_BINARY=node silently fails. Setting the explicit NODE_MODULES_PATH also skips the npm root -g shell-out Browsershot would otherwise run on every single PDF render.

Configuration

All of the following read from config/laravel-pdf.php, already wired to these env vars - nothing in the package needs editing.

Variable Purpose Typical production value
LARAVEL_PDF_DRIVER Which driver builds the PDF browsershot (only driver on this branch)
LARAVEL_PDF_CHROME_PATH Absolute path to the Chrome/Chromium binary /usr/bin/google-chrome-stable
LARAVEL_PDF_NODE_BINARY Override if node isn't on PHP-FPM's PATH absolute path, see nvm note above
LARAVEL_PDF_NODE_MODULES_PATH Skips the npm root -g shell-out at request time absolute path, see nvm note above
LARAVEL_PDF_NO_SANDBOX Adds Chrome's --no-sandbox flag false - only true if running as root in a container (see Security)

Security

  • PHP 7.1 receives no security patches (EOL since December 2019). Keep this service behind a reverse proxy, scoped to only the routes it needs, and track a real upgrade path off PHP 7.1 - this guide doesn't change that fact.
  • Rendering untrusted HTML is a real attack surface. Headless Chrome fetches anything referenced by the HTML it's given - remote images, stylesheets, fonts - and can be pointed at internal network addresses (SSRF) if the input isn't trusted. Sanitize any user-supplied HTML or view data before rendering it to PDF.
  • --no-sandbox is a trade-off, not a formality. It disables a real isolation boundary between a compromised renderer process and the host. Prefer running the service as a genuine non-root user (see the Dockerfile above) so Chrome's sandbox can initialize normally, instead of reaching for this flag.
  • Give the render process a memory ceiling and a timeout (->timeout($seconds) on the builder, plus your process supervisor's memory limit). A stuck or oversized headless Chrome instance is the most common way this kind of service falls over.

Verifying the setup

php -v                                                        # 7.1.x
node -v                                                       # >= 10.18.1
npm ls -g puppeteer                                           # 13.7.0
google-chrome-stable --headless --disable-gpu --version       # no missing-library errors

Then hit a real PDF-generating route and confirm the response starts with %PDF- and, separately, that a ->base64() call also decodes to something starting with %PDF- (confirms the puppeteer pin, not just file-based saves which work even with the buggy version).

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.