# HTML to PDF in PHP (and Laravel)

Convert HTML to PDF in PHP with Dompdf, mPDF or Browsershot, with working code, plus a one-call API option for plain PHP and Laravel.

PHP has several ways to turn HTML into a PDF. The right one depends on how modern your CSS is.

Pure-PHP libraries are easy to install but don't support flexbox or grid. Browser-based tools render like Chrome but need Chrome on the server. This guide covers both, then a third option that needs neither.

**Quick answer.** Send your HTML to the html-pdf.com API and save the bytes it returns. No Chrome or PDF library is needed on your server.

```bash
curl -X POST https://html-pdf.com/v1/pdf \
  -H "Authorization: Bearer $HTML_PDF_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Invoice #042</h1>"}' \
  -o invoice.pdf
```

## Which PHP HTML to PDF library?

This table shows how each tool renders, what CSS it handles and what you need to install.

| Tool | How it renders | Modern CSS (flexbox, grid) | JavaScript | Install |
|---|---|---|---|---|
| Dompdf | Own PHP layout engine | No (mostly CSS 2.1) | No | `composer require dompdf/dompdf` |
| mPDF | Own PHP layout engine | No | No | `composer require mpdf/mpdf` |
| Spatie Browsershot | Headless Chrome via Puppeteer | Yes | Yes | Composer + Node + Puppeteer + Chrome |
| wkhtmltopdf (via Snappy) | Old QtWebKit | Partial | Old engine | Binary, project archived |
| html-pdf.com API | Headless Chromium, hosted | Yes | Yes | Nothing (cURL or Guzzle) |

## Option 1: Dompdf

Dompdf is the most used pure-PHP option. Use it for simple, table-based documents.

```php
<?php
require 'vendor/autoload.php';

use Dompdf\Dompdf;
use Dompdf\Options;

$options = new Options();
$options->set('isRemoteEnabled', true); // allow https images and fonts

$dompdf = new Dompdf($options);
$dompdf->loadHtml('<h1>Invoice #042</h1><p>Total: $120.00</p>');
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();

file_put_contents('invoice.pdf', $dompdf->output());
// or stream to the browser: $dompdf->stream('invoice.pdf', ['Attachment' => false]);
```

Dompdf has no flexbox or grid, so layouts built with modern CSS collapse. Large tables can be slow and use a lot of memory, and you need to register custom fonts yourself. Only enable `isRemoteEnabled` if you trust the HTML.

## Option 2: mPDF

mPDF works from PHP alone. It handles headers, footers and right-to-left or CJK text well.

```php
<?php
require 'vendor/autoload.php';

$mpdf = new \Mpdf\Mpdf(['format' => 'A4', 'margin_top' => 15, 'margin_bottom' => 20]);
$mpdf->SetHTMLFooter('<div style="text-align:center;font-size:9px">Page {PAGENO} of {nbpg}</div>');
$mpdf->WriteHTML('<h1>Invoice #042</h1><p>Total: $120.00</p>');
$mpdf->Output('invoice.pdf', \Mpdf\Output\Destination::FILE);
```

Like Dompdf, it uses its own layout engine, so a page that looks right in Chrome may not look the same in the PDF.

## Option 3: Browsershot (headless Chrome)

Browsershot gives you Chrome-quality PDFs. It calls Puppeteer from PHP, so the PDF matches Chrome's print output.

```bash
composer require spatie/browsershot
npm install puppeteer
```

```php
<?php
use Spatie\Browsershot\Browsershot;

Browsershot::html('<h1>Invoice #042</h1>')
    ->format('A4')
    ->margins(15, 15, 15, 15)
    ->showBackground()
    ->save('invoice.pdf');
```

The catch is running it. Your PHP servers now need Node.js, Puppeteer, a Chrome build with its system libraries, and enough memory to run a browser per request. On shared hosting that often isn't possible.

## Option 4: one API call, no Chrome on your server

html-pdf.com renders your HTML in headless Chromium and returns the PDF. You get Browsershot-quality output with nothing but PHP's cURL extension.

```php
<?php
$html = '<html><head><style>
  .row { display: flex; justify-content: space-between }
</style></head><body>
  <h1>Invoice #042</h1>
  <div class="row"><span>Design work</span><span>$120.00</span></div>
</body></html>';

$ch = curl_init('https://html-pdf.com/v1/pdf');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 60,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('HTML_PDF_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'html' => $html,
        'format' => 'A4',
        'margin' => '15mm',
        'footer_html' => '<div style="font-size:9px;width:100%;text-align:center">'
            . 'Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
    ]),
]);
$pdf = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status !== 200) {
    $err = json_decode($pdf, true)['error'] ?? [];
    throw new RuntimeException(($err['code'] ?? 'error') . ': ' . ($err['message'] ?? ''));
}
file_put_contents('invoice.pdf', $pdf);
```

To convert a public web page instead, send `'url' => 'https://example.com'`. Add `'response' => 'url'` to get JSON with a download link valid for 24 hours.

## Laravel: render a Blade view to PDF

Here you get a Laravel route that returns a Blade view as a PDF. Laravel's HTTP client keeps it short, so render the view to HTML and send it.

```php
<?php
// routes/web.php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;

Route::get('/invoices/{invoice}/pdf', function (App\Models\Invoice $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, // fail without charge if it runs longer
        ]);

    abort_unless($response->successful(), 502, $response->json('error.message'));

    return response($response->body(), 200, [
        'Content-Type' => 'application/pdf',
        'Content-Disposition' => "inline; filename=\"invoice-{$invoice->id}.pdf\"",
    ]);
});
```

Add `'html_pdf' => ['key' => env('HTML_PDF_KEY')]` to `config/services.php`. Blade's double-curly-brace echo escapes output, which keeps customer data from breaking your HTML. Use absolute `https://` URLs for images and CSS in the view, or inline them, since the renderer can't reach `localhost` or private networks.

## Get an API key

One call gives you a key.

```bash
curl -X POST https://html-pdf.com/v1/signup -H "Content-Type: application/json" -d '{"email": "you@example.com"}'
```

Open the link we email you to confirm your address. That adds 100 free credits, and 1 credit = 1 PDF page. Failed renders cost nothing, and purchased credits never expire. Read the response headers `X-Pages` and `X-Credits-Remaining` to track usage.

Coming from wkhtmltopdf or Snappy? See [wkhtmltopdf alternative](https://html-pdf.com/wkhtmltopdf-alternative). For pagination, see [page breaks](https://html-pdf.com/html-to-pdf-page-breaks). The full reference is in the [API docs](https://html-pdf.com/docs).

## FAQ

### How do I convert HTML to PDF in PHP?

Use Dompdf or mPDF for simple layouts, or Browsershot if you can run Chrome on the server. For Chrome-quality output with only cURL, POST your HTML to `https://html-pdf.com/v1/pdf` and save the response.

### Does Dompdf support flexbox or grid?

No. Dompdf and mPDF mostly support CSS 2.1, so flexbox and grid layouts break. Browsershot and the html-pdf.com API render with Chromium, so modern CSS works.

### Is there a free HTML to PDF API for PHP?

html-pdf.com gives you 100 free credits once, with no card needed. 1 credit = 1 PDF page, and failed renders cost nothing.

### Do credits expire?

No. Purchased credits never expire. Packs go from $10 for 1,000 pages to $250 for 50,000 pages.

