HTML to PDF in PHP and Laravel
, 5 min read
PHP has no headless browser of its own, so HTML-to-PDF usually means a wkhtmltopdf binary, a Chrome install, or an HTTP API. This guide does the last, first in plain PHP with cURL, then with Laravel’s HTTP client.
Plain PHP
<?php
$ch = curl_init("https://api.pagenull.dev/v1/pdf");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("PAGENULL_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"html" => "<h1>Invoice #1001</h1><p>Total: €120.00</p>",
"format" => "A4",
"margin" => ["top" => "20mm", "bottom" => "20mm"],
]),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status !== 200) {
$error = json_decode($body, true)["error"];
throw new RuntimeException($error["code"] . ": " . $error["message"]);
}
file_put_contents("invoice.pdf", $body);Laravel
Put the key in .env as PAGENULL_API_KEY and read it through config/services.php:
'pagenull' => [
'key' => env('PAGENULL_API_KEY'),
],<?php
namespace App\Http\Controllers;
use App\Models\Invoice;
use Illuminate\Support\Facades\Http;
class InvoicePdfController extends Controller
{
public function show(Invoice $invoice)
{
$html = view('invoices.pdf', ['invoice' => $invoice])->render();
$res = Http::withToken(config('services.pagenull.key'))
->timeout(60)
->post('https://api.pagenull.dev/v1/pdf', ['html' => $html, 'format' => 'A4']);
abort_unless($res->successful(), 502, $res->json('error.message'));
return response($res->body(), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="invoice-'.$invoice->number.'.pdf"',
]);
}
}A Blade view renders the HTML, so the invoice layout lives with your other views. Inline CSS or a <style> block in the view is simplest; public stylesheet URLs also work.
Generating in a queue
For batches such as month-end invoices, render in a queued job and store the file under your own retention rules. pagenull keeps no copy, so this is the copy:
public function handle(): void
{
$res = Http::withToken(config('services.pagenull.key'))
->withHeaders(['Idempotency-Key' => 'invoice-'.$this->invoice->id])
->timeout(60)
->post('https://api.pagenull.dev/v1/pdf', ['html' => view('invoices.pdf', ['invoice' => $this->invoice])->render()]);
$res->throw();
Storage::put("invoices/{$this->invoice->number}.pdf", $res->body());
$this->invoice->update(['pdf_receipt' => $res->header('pagenull-receipt')]);
}With the Idempotency-Key, a retried job is rendered again but billed once. Laravel retries failed jobs for you; with this header that is safe. The receipt is worth keeping: here’s why.