Use case: invoices
Invoice PDF generation from an HTML template
If you already render invoices as a web page or an email, you are most of the way to a PDF. Keep one HTML template, fill it with each customer’s data, and convert it to a PDF automatically when an order is paid.
INV-1042 · Due 24 Oct 2026
| Description | Amount |
|---|---|
| Website design, homepage | $1,200.00 |
| Hosting, 12 months | $240.00 |
| Extra revision rounds (3) | $180.00 |
| Total due (incl. 8% tax) | $1,749.60 |
The HTML template to PDF workflow
Design one HTML template
Build the invoice as a normal HTML page with inline CSS. Preview it in a browser while you design.
Fill it with order data
On your server, merge each invoice’s data into the template, escaping every dynamic value.
Convert, then store
Send the HTML to the API, decode the PDF, and save it. Attach it to an email or offer it as a download.
Try the sample invoice
The converter on this page is real. Load the sample invoice, edit it, and download the PDF to see how your template will look.
The sample sets @page { size: A4; margin: 16mm }, so the PDF is A4 regardless of the width and height values. Remove that rule to size the page using the width and height fields instead.
Dynamic invoice data and template considerations
Escape everything dynamic
Customer names, addresses and notes can contain < or &. Escape them when merging into HTML so they cannot break the layout or inject markup.
Store money as integers
Keep amounts in cents and format them only when rendering, to avoid rounding drift.
Use absolute image URLs
A logo must be reachable over the public internet, or embedded as a data: URI.
Inline your CSS
Put styles in a <style> tag in the same document, so there is nothing else to fetch.
Keep totals computed in one place
Calculate tax and totals in code, not in the template, so what you store matches what the PDF shows.
Version your template
Store the invoice data, not just the PDF, so you can regenerate a document if the layout changes.
Integration example in PHP
The template is a plain PHP file that outputs HTML. The caller renders it with output buffering and passes the string to the client from the PHP integration guide. Using Laravel? The same flow works with a Blade view and a queued job, shown in the Laravel guide.
<?php
// invoice-template.php: plain PHP used as a template. $invoice is provided by the caller.
$money = fn (int $cents) => '$' . number_format($cents / 100, 2);
$e = fn ($v) => htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8'); // always escape dynamic data
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8"><style>/* styles from the sample invoice */</style></head>
<body>
<h1>Invoice <?= $e($invoice['number']) ?></h1>
<p>Billed to <?= $e($invoice['customer']['name']) ?></p>
<table>
<?php foreach ($invoice['lines'] as $line): ?>
<tr>
<td><?= $e($line['description']) ?></td>
<td class="num"><?= (int) $line['qty'] ?></td>
<td class="num"><?= $money($line['qty'] * $line['unit_cents']) ?></td>
</tr>
<?php endforeach; ?>
</table>
<p>Total due: <?= $money($invoice['total_cents']) ?></p>
</body></html><?php
require __DIR__ . '/CheapPdfClient.php'; // from the PHP integration guide
function renderInvoiceHtml(array $invoice): string
{
ob_start();
include __DIR__ . '/invoice-template.php';
return ob_get_clean();
}
$client = new CheapPdfClient(getenv('CHEAPPDF_API_KEY'));
$pdf = $client->fromHtml(
renderInvoiceHtml($invoice),
"invoice-{$invoice['number']}.pdf"
);
file_put_contents(__DIR__ . "/storage/invoices/{$invoice['number']}.pdf", $pdf);Batch processing and failure recovery
Month-end runs create many invoices at once. Make the job idempotent so it can be re-run safely: select only invoices without a stored PDF, write each file atomically, and record failures instead of aborting the whole run.
<?php
// Nightly job: create PDFs for invoices that do not have one yet. Safe to re-run.
foreach (fetchInvoicesWithoutPdf() as $invoice) { // WHERE pdf_path IS NULL
$target = __DIR__ . "/storage/invoices/{$invoice['number']}.pdf";
for ($attempt = 1; $attempt <= 3; $attempt++) {
try {
$pdf = $client->fromHtml(renderInvoiceHtml($invoice), "{$invoice['number']}.pdf");
file_put_contents($target . '.tmp', $pdf);
rename($target . '.tmp', $target); // never leave a half-written file
markInvoicePdfCreated($invoice['id'], $target);
continue 2; // next invoice
} catch (CheapPdfException $e) {
if ($e->httpStatus === 400) { // bad key, quota, bad input: retrying won't help
logFailure($invoice['id'], $e->errorCode ?? $e->getMessage());
continue 2;
}
sleep(2 ** $attempt); // network or 5xx: back off, then retry
}
}
logFailure($invoice['id'], 'gave up after 3 attempts'); // picked up again on the next run
}Mind your plan. The free plan allows 10 conversions per day, so a batch of invoices needs the Premium plan. If the API returns free_plan_limit_reached, stop the run rather than retrying every invoice. Request rate limits are not published, so process invoices one at a time or with low concurrency.
Document formatting considerations
| Goal | How | Notes |
|---|---|---|
| A4 or Letter page | @page { size: A4; margin: 16mm } | Honoured by the renderer, so the PDF takes the CSS page size. |
| Page size from the request | Send width and height in pixels | 800 × 1000 px produces a 600 × 750 pt page. |
| Keep a row on one page | tr { break-inside: avoid } | Prevents a line item splitting across pages. |
| Force a page break | break-before: page | Starts the next section, for example terms, on a new page. |
| Repeat a header on every page | A position: fixed element at the top | Repeated on every page in our test. Leave space for it with body padding or a page margin. |
| Page numbers (“Page 1 of 3”) | Not available | No page-counter option is exposed, so we do not claim numbering. Most invoices fit on one page; for multi-page documents, number sections in your own content. |
Invoice PDF generation FAQ
How do I generate an invoice PDF from HTML?
Build the invoice as an HTML template, merge in the order data on your server, and send the resulting HTML to the CheapPDF API. The response contains the PDF, which you decode and save or email.
Can I use my existing email or web invoice as the template?
Yes, if it is self-contained HTML. Inline the CSS and use absolute image URLs, then preview the PDF and adjust page size and breaks.
Can I create many invoices at once?
Yes. Loop over your invoices with a small concurrency, store each result, and keep a record of failures to retry. Batches need the Premium plan because the free plan allows 10 conversions per day.
Is the PDF compliant for tax or accounting purposes?
CheapPDF converts the HTML you provide into a PDF. It does not validate invoice content, so confirm the legal requirements for your region separately.
Can I add a company logo?
Yes. Use an image with a public absolute URL, or embed it as a data: URI in the template.
Automate your invoices
Create an API key and generate your first invoice PDF from a template.