# HTML to PDF in Laravel HTML to PDF in Laravel: render Blade views with laravel-dompdf, Browsershot, spatie/laravel-pdf, or one Http::withToken() call to an API. Laravel makes it easy to render a Blade view to HTML. Turning that HTML into a PDF is where you have to choose. A pure-PHP renderer is simple to install but ignores modern CSS, while headless Chrome renders perfectly but has to run somewhere. This guide compares the main packages, then shows a version with no browser on your server. **Quick answer.** Render the view, post it to the API with Laravel's HTTP client, and return the bytes: ```php $response = Http::withToken(config('services.html_pdf.key')) ->post('https://html-pdf.com/v1/pdf', ['html' => view('invoices.pdf', ['invoice' => $invoice])->render()]); return response($response->body(), 200, ['Content-Type' => 'application/pdf']); ``` ## Laravel PDF packages compared This table shows which engine each option uses and what it needs on your server. | Package | Engine | Flexbox / grid | Server requirements | |---|---|---|---| | barryvdh/laravel-dompdf | Dompdf (pure PHP) | No | None beyond PHP extensions | | spatie/browsershot | Puppeteer + Chrome | Yes | Node.js, Puppeteer, Chrome | | spatie/laravel-pdf | Driver-based: Browsershot, Cloudflare, DomPdf, Gotenberg | Depends on driver | Depends on driver | | Http client + html-pdf.com | Headless Chromium, hosted | Yes | Nothing | ## Option 1: barryvdh/laravel-dompdf This is the most installed choice and the quickest to set up. It wraps Dompdf in a Laravel facade. ```bash composer require barryvdh/laravel-dompdf ``` ```php use Barryvdh\DomPDF\Facade\Pdf; public function download(Invoice $invoice) { return Pdf::loadView('invoices.pdf', ['invoice' => $invoice]) ->setPaper('a4') ->download("invoice-{$invoice->id}.pdf"); } ``` Dompdf implements roughly CSS 2.1, so layouts built with flexbox, grid or Tailwind utilities that depend on them collapse. Use tables and floats in PDF views instead. Large documents can be slow and memory-hungry. Remote images need the `isRemoteEnabled` option, which you should only turn on for HTML you trust. ## Option 2: spatie/browsershot This option gives you PDFs that match Chrome's print output. Browsershot drives headless Chrome through Puppeteer. ```bash composer require spatie/browsershot npm install puppeteer ``` ```php use Spatie\Browsershot\Browsershot; $html = view('invoices.pdf', ['invoice' => $invoice])->render(); $pdf = Browsershot::html($html) ->format('A4') ->margins(15, 15, 15, 15) ->showBackground() ->pdf(); // raw bytes return response($pdf, 200, ['Content-Type' => 'application/pdf']); ``` The cost is your infrastructure. Every server or worker needs Node.js, Puppeteer, a Chrome build and its system libraries, plus enough memory to start a browser per render. Forge and Docker setups can handle it. Shared hosting and some serverless platforms can't. ## Option 3: spatie/laravel-pdf This option gives you a Laravel-style API on top of a choice of drivers. Browsershot is the default. Version 2 added Cloudflare Browser Rendering and DomPdf drivers, and v2.1 added Gotenberg. ```php use Spatie\LaravelPdf\Facades\Pdf; public function download(Invoice $invoice) { return Pdf::view('invoices.pdf', ['invoice' => $invoice]) ->format('a4') ->name("invoice-{$invoice->id}.pdf") ->download(); } ``` It is a clean API, but each driver keeps its own requirements. That means Chrome for Browsershot, a Cloudflare account, a running Gotenberg container, or Dompdf's CSS limits. ## Or skip the server: Http::withToken() to html-pdf.com This option gives you Chrome-quality PDFs with nothing to install. html-pdf.com runs headless Chromium for you. Your app renders the Blade view, sends the HTML with Laravel's HTTP client, and gets the PDF bytes back in the response. Get a key, then open the link we email you to confirm your address. That adds 100 free credits. ```bash curl -X POST https://html-pdf.com/v1/signup -H "Content-Type: application/json" -d '{"email": "you@example.com"}' ``` ```php // config/services.php 'html_pdf' => [ 'key' => env('HTML_PDF_KEY'), ], ``` ```php authorize('view', $invoice); $html = view('invoices.pdf', ['invoice' => $invoice])->render(); $response = Http::withToken(config('services.html_pdf.key')) ->timeout(60) ->post('https://html-pdf.com/v1/pdf', [ 'html' => $html, 'format' => 'A4', 'margin' => '15mm', 'max_pages' => 20, // longer documents fail without charge 'footer_html' => '
' . 'Page of
', ]); if ($response->failed()) { report(new \RuntimeException('PDF failed: ' . $response->json('error.code'))); abort(502, $response->json('error.message') ?? 'Could not render PDF'); } return response()->streamDownload( fn () => print($response->body()), "invoice-{$invoice->id}.pdf", ['Content-Type' => 'application/pdf'] ); } } ``` ```php // routes/web.php Route::get('/invoices/{invoice}/pdf', \App\Http\Controllers\InvoicePdfController::class) ->middleware('auth'); ``` To show the PDF in the browser instead of downloading it, return `response($response->body(), 200, ['Content-Type' => 'application/pdf', 'Content-Disposition' => 'inline; filename="invoice.pdf"'])`. ## The Blade view This section shows a PDF view that uses modern CSS. Flexbox and other modern CSS work because the renderer is Chromium. ```blade {{-- resources/views/invoices/pdf.blade.php --}}

Invoice {{ $invoice->number }}

@foreach ($invoice->lines as $line)
{{ $line->label }}{{ $line->amount }}
@endforeach ``` Blade's double-curly-brace echo escapes output, which keeps customer data from breaking the HTML. `asset()` must resolve to a public `https://` URL, because the renderer can't reach `localhost` or a private network. In local development, inline images as data URIs. You can also send the view and its assets as a multipart request (`index.html` plus files, with your options in an `options` JSON field). If you use Vite or Tailwind, read the built CSS file and pass it as `css`. ## Queues, credits and errors This section covers running PDFs in the background and handling credits. Rendering takes a second or more, so for emails or batch exports, generate inside a queued job and store the bytes with `Storage::put()`. Responses include `X-Pages`, `X-Credits-Used` and `X-Credits-Remaining` headers. A `402` with `error.code` `insufficient_credits` means it's time to top up, or to enable auto top-up. - **Pricing:** 1 credit = 1 PDF page. 100 free credits, then packs from $10 for 1,000 pages. Purchased credits never expire and failed renders are free. - **Limits:** 20 MB request, 30 seconds per render, 120 requests per minute. More: [HTML to PDF in PHP](https://html-pdf.com/html-to-pdf-php) · [page breaks](https://html-pdf.com/html-to-pdf-page-breaks) · [headers, footers and page numbers](https://html-pdf.com/pdf-headers-footers-page-numbers) · [API docs](https://html-pdf.com/docs) ## FAQ ### How do I generate a PDF from a Blade view in Laravel? Render the view with `view('name', $data)->render()` and pass the HTML to a PDF engine. That can be laravel-dompdf, Browsershot, spatie/laravel-pdf, or a `Http::withToken()` call to `https://html-pdf.com/v1/pdf`. ### Why does my Tailwind or flexbox layout break with laravel-dompdf? Dompdf supports roughly CSS 2.1, so flexbox and grid aren't rendered. Switch to tables and floats, or use a Chromium-based option like Browsershot or the API. ### Can I generate PDFs in Laravel without installing Chrome? Yes. Send the rendered HTML to html-pdf.com, which runs headless Chromium for you, so your server only needs Laravel's HTTP client. ### Do credits expire? Purchased credits never expire. You start with 100 free credits, 1 credit is 1 PDF page, and failed renders cost nothing.