PDFPipe

Guide

How to generate a PDF from HTML in Laravel

Laravel apps frequently need to turn a Blade view into a downloadable PDF: invoices, receipts, reports, tickets. You can render the PDF inside your own process with a library like dompdf or snappy, or you can POST the HTML to a rendering API and stream the result back to the browser. This guide shows the API approach with a clean controller, then explains exactly when a local library is the better call.

The quickest path: render a Blade view, POST the HTML

The pattern is simple. Render any Blade template to an HTML string with view()->render(), send that string to the PDFPipe endpoint using Laravel HTTP client, and return the raw bytes as a download response. The endpoint is POST https://api.pdfpipe.xyz/v1/pdf, it accepts a JSON body and returns application/pdf.

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class InvoicePdfController extends Controller
{
    public function download(Request $request, Invoice $invoice)
    {
        // 1. Render a Blade view to an HTML string.
        $html = view('invoices.pdf', ['invoice' => $invoice])->render();

        // 2. POST the HTML to the PDFPipe rendering API.
        $response = Http::withToken(config('services.pdfpipe.key'))
            ->withOptions(['stream' => false])
            ->post('https://api.pdfpipe.xyz/v1/pdf', [
                'html' => $html,
                'options' => ['format' => 'A4'],
            ]);

        // 3. Fail loudly if the API returned an error status.
        $response->throw();

        // 4. Return the raw PDF bytes as a download.
        return response($response->body(), 200, [
            'Content-Type' => 'application/pdf',
            'Content-Disposition' => 'attachment; filename="invoice-' . $invoice->id . '.pdf"',
        ]);
    }
}

The Authorization: Bearer pp_live_your_key header is added for you by Http::withToken(). Keep the key in config/services.php so it stays out of version control:

php
// config/services.php
return [
    // ...
    'pdfpipe' => [
        'key' => env('PDFPIPE_KEY'),
    ],
];

// .env
PDFPIPE_KEY=pp_live_your_key

Wire up the route and you are done. No browser binary to install, no PHP extension to compile, no font packages to copy into the container.

php
// routes/web.php
use App\Http\Controllers\InvoicePdfController;

Route::get('/invoices/{invoice}/pdf', [InvoicePdfController::class, 'download'])
    ->name('invoices.pdf');

Streaming the PDF instead of buffering it

For large reports you may not want to hold the entire file in memory. The HTTP client can hand you a PSR stream, which you can pass straight to a StreamedResponse:

php
use Symfony\Component\HttpFoundation\StreamedResponse;

$response = Http::withToken(config('services.pdfpipe.key'))
    ->withOptions(['stream' => true])
    ->post('https://api.pdfpipe.xyz/v1/pdf', [
        'html' => $html,
        'options' => ['format' => 'A4'],
    ]);

return new StreamedResponse(function () use ($response) {
    $body = $response->toPsrResponse()->getBody();
    while (!$body->eof()) {
        echo $body->read(8192);
    }
}, 200, [
    'Content-Type' => 'application/pdf',
    'Content-Disposition' => 'attachment; filename="report.pdf"',
]);

When dompdf or snappy is the better choice

Local libraries are genuinely good tools and the right answer in plenty of cases. barryvdh/laravel-dompdf is pure PHP, so it has zero external dependencies and works offline, which is ideal for simple, text heavy documents. barryvdh/laravel-snappy wraps wkhtmltopdf and handles richer CSS, but it needs that binary present on every machine that runs your app. Here is how the options compare honestly:

Concerndompdfsnappy (wkhtmltopdf)PDFPipe API
Installcomposer onlycomposer plus wkhtmltopdf binarycomposer only (HTTP client)
Modern CSS, flexbox, gridLimitedPartial (old WebKit)Full (current browser engine)
Web fonts and emojiManual font setupManual font setupBuilt in
CPU and memory on your serverYoursYours (heavy)Offloaded
Works fully offlineYesYesNo (network call)
Per document costFreeFreeMetered

For a quick comparison, the equivalent dompdf controller looks like this:

php
use Barryvdh\DomPDF\Facade\Pdf;

public function download(Invoice $invoice)
{
    $pdf = Pdf::loadView('invoices.pdf', ['invoice' => $invoice])
        ->setPaper('a4');

    return $pdf->download('invoice-' . $invoice->id . '.pdf');
}

That is less code and has no network dependency, so reach for it when your documents are straightforward and you want everything to stay inside your own process. Choose the API when your invoices use a real design system, when wkhtmltopdf is fighting your CSS, or when you would rather not run a heavy rendering engine inside your web workers or queue.

Rendering in a queued job

Generating PDFs during a web request blocks the response. For batch exports, move the call into a queued job and store the result. The HTTP call itself is unchanged, you simply return the bytes to disk instead of the browser:

php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;

public function handle(): void
{
    $html = view('invoices.pdf', ['invoice' => $this->invoice])->render();

    $pdf = Http::withToken(config('services.pdfpipe.key'))
        ->post('https://api.pdfpipe.xyz/v1/pdf', [
            'html' => $html,
            'options' => ['format' => 'A4'],
        ])
        ->throw()
        ->body();

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

Paste a Blade rendered HTML string into the live playground on the home page to preview output instantly, then read the full request and option reference in the docs.

See pricing →