HTML to PDF in PHP
One POST, a PDF back. No headless browser to install, no Chromium to keep patched, no fonts to install on the box — the render happens on a warm browser that is already running.
Render HTML to a PDF
<?php
$payload = json_encode([
'html' => '<h1>Invoice 1042</h1>',
'options' => ['printBackground' => true, 'margin' => ['top' => '20mm']],
]);
$ch = curl_init('https://api.pdfcraft.dev/v1/render');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('PDFCRAFT_API_KEY'),
'Content-Type: application/json',
],
]);
$pdf = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException("render failed: {$status} {$pdf}");
}
file_put_contents('invoice.pdf', $pdf);Read a PDF back as JSON
The same key works for extraction. Send a PDF, name the fields you want by the label printed on the page, and get them back with the raw text, a coerced value, a confidence and a bounding box.
<?php
$payload = json_encode([
'file' => base64_encode(file_get_contents('statement.pdf')),
'schema' => ['account_number' => 'string', 'closing_balance' => 'currency'],
'options' => ['rows_as_objects' => true],
]);
$ch = curl_init('https://api.pdfcraft.dev/v1/extract');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('PDFCRAFT_API_KEY'),
'Content-Type: application/json',
],
]);
$body = json_decode(curl_exec($ch), true);
echo $body['fields']['closing_balance']['value'];What bites in PHP
- CURLOPT_RETURNTRANSFER must be true or curl_exec prints the PDF straight to the output buffer — which, in a web request, means the binary lands in the HTTP response and the browser shows mojibake.
- Do not set CURLOPT_POSTFIELDS to an array when the body is JSON. PHP switches to multipart/form-data and the API rejects it as an invalid body.
- Set CURLOPT_TIMEOUT. The default is unlimited, and PHP-FPM will kill the worker long before curl gives up.
Errors are one shape
Every failure is {"error":{"code","message","docs_url"}} with a stable code, so you can switch on error.code rather than parsing prose. The two worth handling explicitly are rate_limited — honour Retry-After — and render_failed, which means your HTML broke rather than ours did.
The same thing in another language
- HTML to PDF in Node.js
- HTML to PDF in Python
- HTML to PDF in Go
- HTML to PDF in Ruby
- HTML to PDF in Java
- HTML to PDF in C#
- HTML to PDF in Rust
- HTML to PDF in curl
- HTML to PDF in Deno
- HTML to PDF in Bun
- HTML to PDF in TypeScript
Or try it with no code at all in the playground. A free key is 100 renders a month.