Skip to content
PDFDispatch

HTML to PDF in Laravel

If your invoice or report is already a Blade view, one HTTP request turns it into a PDF. Laravel's own HTTP client sends it, so you do not install Chrome, wkhtmltopdf or a PHP extension.

What you need

  • A Laravel app (any version with Illuminate\Support\Facades\Http, so 7 or newer).
  • A PDFDispatch key. Make one at pdfdispatch.com/app/keys. It starts with pdd_.

Put the key in .env:

PDFDISPATCH_KEY=pdd_your_key_here

And read it in config/services.php, so it also works with a cached config:

'pdfdispatch' => [
    'key' => env('PDFDISPATCH_KEY'),
],

The view

Write the view as a normal web page. The browser that prints it is Chromium, the core of Google Chrome, so flexbox, grid and web fonts all work. Two lines of CSS do most of the print layout:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <style>
            @page {
                size: A4;
                margin: 18mm 15mm;
            }
            body {
                font-family: 'Noto Sans', sans-serif;
                font-size: 11pt;
            }
            .totals {
                display: grid;
                grid-template-columns: 1fr auto;
                gap: 4px 24px;
            }
            tr {
                break-inside: avoid;
            }
        </style>
    </head>
    <body>
        <h1>Invoice {{ $invoice->number }}</h1>
        {{-- ... --}}
    </body>
</html>

@page sets the paper and the margins. break-inside: avoid keeps a table row from being cut in half at the end of a page.

The request

Render the view to a string and send it as a file named index.html:

use Illuminate\Support\Facades\Http;

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

$response = Http::withToken(config('services.pdfdispatch.key'))
    ->timeout(90)
    ->attach('files', $html, 'index.html')
    ->post('https://api.pdfdispatch.com/v1/convert/html', [
        'preferCssPageSize' => 'true',
        'printBackground' => 'true',
    ]);

$response->throw();

$pdf = $response->body();

preferCssPageSize tells the API to use the paper size from your @page rule. printBackground keeps background colours, which browsers leave out when they print.

$response->throw() turns an error into an exception. Errors from the gateway (a wrong key, the plan's limit) are JSON with a code; errors in the request itself are a 400 with a short text. Limits and errors lists them all.

Send it to the browser or save it

To download it from a controller:

return response($pdf, 200, [
    'Content-Type' => 'application/pdf',
    'Content-Disposition' => 'attachment; filename="invoice-'.$invoice->number.'.pdf"',
]);

To keep it, for example to attach it to an e-mail later:

use Illuminate\Support\Facades\Storage;

Storage::put("invoices/{$invoice->number}.pdf", $pdf);

If you make many PDFs at once, for example all invoices at the end of the month, do it in a queued job. Each request waits for its PDF, and a queue keeps that wait out of your web requests.

Images, stylesheets and fonts

Anything on the public internet works as it is: an image URL or a font from a CDN. Files that are only on your server must go in the same request. Attach each one and refer to it by its bare file name:

Http::withToken(config('services.pdfdispatch.key'))
    ->attach('files', $html, 'index.html')
    ->attach('files', file_get_contents(public_path('img/logo.png')), 'logo.png')
    ->attach('files', file_get_contents(resource_path('css/invoice.css')), 'invoice.css')
    ->post('https://api.pdfdispatch.com/v1/convert/html');

In the view, that is <img src="logo.png"> and <link rel="stylesheet" href="invoice.css">. Small images can also be inline as data: URLs.

Common fonts are already installed, so you can use them by name: Noto (also for Chinese, Japanese, Korean and emoji), DejaVu, and Liberation, which has the same letter widths as Arial, Times New Roman and Courier New. Any other font, attach as a file and load it with @font-face.

Page numbers

A header or footer on every page is two more small HTML files, header.html and footer.html. The browser fills in <span class="pageNumber"></span> and <span class="totalPages"></span> on each page. Leave room for them with a bigger marginTop or marginBottom. Header and footer has the details and an example.

Why not dompdf?

dompdf runs inside PHP, which is handy. But it has its own layout engine, with no flexbox or grid, so a view that looks right in the browser often looks wrong in the PDF. Packages that drive headless Chrome (Browsershot, for example) get the layout right but need Chrome and Node.js on every server that makes PDFs. wkhtmltopdf is no longer maintained. With an API, the browser runs on our servers instead of yours.

Next

  • HTML to PDF: every field of this route.
  • Rendering options: paper, margins, and waiting for charts drawn with JavaScript.
  • PDF tools: PDF/A for archives, passwords, watermarks, merging.