PHP's HTML-to-PDF landscape splits cleanly: pure-PHP renderers (Dompdf, TCPDF, mPDF) are easy to install but support a 2010-era CSS subset. wkhtmltopdf uses WebKit and handles modern CSS, but needs a system binary and a virtual display. Which to choose depends on how complex your HTML is: Dompdf for invoice templates, wkhtmltopdf for anything with background images or flexbox.

Method 1: Dompdf (pure PHP, CSS 2.1 subset)

Dompdf is the most widely used PHP PDF library. It handles most invoice and report templates well. Modern CSS (flexbox, grid, CSS variables) is not supported.

composer require dompdf/dompdf
setChroot(realpath('./'));
    $options->setIsRemoteEnabled(false); // disable for security
    $options->setIsHtml5ParserEnabled(true);
    $options->setDefaultFont('DejaVu Sans'); // handles Unicode better than Helvetica

    $dompdf = new Dompdf($options);
    $dompdf->loadHtml($html);
    $dompdf->setPaper('A4', 'portrait');
    $dompdf->render();

    file_put_contents($outPath, $dompdf->output());
}

$html = <<




Invoice #1234

ItemAmount
Widget$49.00
HTML; htmlToPdf($html, './output/invoice.pdf'); echo "Done\n";

Dompdf tips:

  • Use font-family: DejaVu Sans for best Unicode support. Helvetica and Arial lack many characters.
  • Disable setIsRemoteEnabled when processing user HTML — it prevents SSRF attacks via external CSS/image URLs.
  • Dompdf does not support float-based multi-column layouts reliably. Use tables for multi-column content.

Method 2: wkhtmltopdf (WebKit engine, full CSS support)

wkhtmltopdf renders HTML using WebKit — the same engine as older Safari. It handles modern CSS, backgrounds, and @media print rules correctly.

# Ubuntu/Debian
apt install wkhtmltopdf
# Or download binary from https://wkhtmltopdf.org
wkhtmltopdf --version
 'A4',
        '--margin-top' => '15mm',
        '--margin-bottom' => '15mm',
        '--margin-left' => '15mm',
        '--margin-right' => '15mm',
        '--encoding' => 'UTF-8',
        '--no-outline' => null,
        '--disable-javascript' => null, // security: don't run JS in user HTML
    ];
    $opts = array_merge($defaultOptions, $options);

    $flagStr = '';
    foreach ($opts as $flag => $value) {
        $flagStr .= ' ' . escapeshellarg($flag);
        if ($value !== null) {
            $flagStr .= ' ' . escapeshellarg($value);
        }
    }

    $cmd = "wkhtmltopdf{$flagStr} " . escapeshellarg($tmpHtml) . ' ' . escapeshellarg($outPath) . ' 2>&1';
    $output = shell_exec($cmd);
    unlink($tmpHtml);

    if (!file_exists($outPath)) {
        throw new RuntimeException("wkhtmltopdf failed: {$output}");
    }
}

htmlToPdfWk('

Hello World

', './output/result.pdf'); echo "Done\n";

Always pass --disable-javascript when processing user-supplied HTML to prevent JS execution. wkhtmltopdf may need --no-sandbox when running as root (inside Docker) — add it only if needed.

Method 3: ChangeThisFile API (Guzzle, no binary installs)

No wkhtmltopdf or Dompdf in your environment? The ChangeThisFile API accepts HTML files and returns PDFs. Source is auto-detected from the filename. Free tier: 1,000 conversions/month, no card required.

composer require guzzlehttp/guzzle
 60]);
    $response = $client->post('https://changethisfile.com/v1/convert', [
        'headers' => ['Authorization' => 'Bearer ' . $apiKey],
        'multipart' => [
            ['name' => 'file', 'contents' => fopen($htmlPath, 'r'), 'filename' => basename($htmlPath)],
            ['name' => 'target', 'contents' => 'pdf'],
        ],
    ]);
    file_put_contents($outPath, $response->getBody()->getContents());
}

// Write HTML to temp file, convert, clean up
$html = '

Report

Content here.

'; $tmpPath = tempnam(sys_get_temp_dir(), 'ctf_') . '.html'; file_put_contents($tmpPath, $html); htmlToPdfApi($tmpPath, './output/report.pdf', 'ctf_sk_your_key_here'); unlink($tmpPath); echo "Done\n";

When to use each

ApproachBest forTradeoff
DompdfSimple invoices, reports — pure PHP, composer installCSS 2.1 subset only; no flexbox/grid/CSS variables
wkhtmltopdfComplex CSS, background images, print-media rulesSystem binary required; no official support for modern sites
ChangeThisFile APIZero local installs, shared hosting, serverless PHPNetwork call, 25MB file limit on free tier

Production tips

  • Use @page CSS for margin and size control in Dompdf. @page { size: A4; margin: 20mm; } is more reliable than Dompdf's PHP API for margin control.
  • Embed images as base64 in Dompdf. External image URLs in Dompdf are unreliable. Convert images to data URIs: 'data:image/png;base64,' . base64_encode(file_get_contents('logo.png')).
  • Pass --load-error-handling ignore to wkhtmltopdf to prevent external resource failures from aborting the conversion.
  • Set Dompdf's chroot to your document root. Without chroot, Dompdf can read arbitrary files from the server when processing user HTML with local file:// references.
  • Use landscape for wide tables. Pass 'landscape' as the second argument to setPaper() in Dompdf, or --orientation Landscape to wkhtmltopdf.

Dompdf wins for simple invoice templates where you control the HTML. wkhtmltopdf is the right call for anything with real CSS. For zero-dependency environments, the API needs only curl. Get a free key covering 1,000 conversions/month.