Billing and receivables / A4
Statement of account HTML template
A periodic summary of one customer's account: opening balance, everything that moved it, closing balance, and how old the outstanding amount is.
When this document is the right one
Sent monthly to customers with an open account. It is the document that prompts payment without being a chaser, and it is what a customer reconciles their purchase ledger against.
Why each part is there
The fields are visible in the markup. What is not visible is why they are present, which is the part that gets removed first when someone adapts a template for their own use.
- Opening balance, movements, closing balance. A statement that lists open invoices without the balance arithmetic is a list, not a statement.
- A running balance column, so the reader can find the point where the two ledgers diverge instead of comparing totals and guessing.
- Credit notes and payments in the same sequence as invoices, in date order, because that is the order the customer's ledger is in.
- An aged analysis, which is the part a collections team acts on. Open invoices with no age on them tell nobody anything.
- Payment instructions, because a statement that prompts a payment should not require the reader to find an invoice to know where to send it.
The template
A complete file. Doctype, stylesheet, body: paste it into a renderer unchanged and it produces a finished A4 page. Replace the sample data and the styling holds.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Statement of account, May 2026</title>
<style>
@page { size: A4; margin: 15mm 14mm 20mm; }
* { box-sizing: border-box; }
body { margin: 0; font: 9pt/1.4 "Helvetica Neue", Arial, sans-serif; color: #111; }
header { display: flex; justify-content: space-between; align-items: flex-start;
border-bottom: 2px solid #111; padding-bottom: 6mm; }
h1 { margin: 0; font-size: 17pt; }
.period { text-align: right; font-size: 9pt; }
.summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4mm;
margin: 7mm 0; break-inside: avoid; }
.summary div { border: 1px solid #ddd; padding: 3.5mm; }
.summary .k { font-size: 7.5pt; text-transform: uppercase; letter-spacing: 0.06em;
color: #666; }
.summary .v { font-size: 13pt; font-weight: 700; font-variant-numeric: tabular-nums;
margin-top: 1.5mm; }
h2 { font-size: 9pt; text-transform: uppercase; letter-spacing: 0.08em; color: #666;
margin: 8mm 0 2mm; }
table { width: 100%; border-collapse: collapse; }
thead { display: table-header-group; }
th { text-align: left; font-size: 7.5pt; text-transform: uppercase; color: #666;
letter-spacing: 0.06em; border-bottom: 1px solid #111; padding-bottom: 1.5mm; }
td { padding: 2mm 0; border-bottom: 1px solid #f0f0f0; }
tr { break-inside: avoid; }
.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
tfoot td { border-top: 1.5px solid #111; border-bottom: 0; font-weight: 700;
padding-top: 2.5mm; }
/* Aging is the point of a statement. A list of open invoices with no age on
them tells a collections team nothing they did not already have. */
.aging { margin-top: 8mm; break-inside: avoid; }
.aging table { width: 100%; }
.aging th, .aging td { border: 1px solid #ddd; padding: 2mm; text-align: right; }
.aging th:first-child, .aging td:first-child { text-align: left; }
.aging .overdue { background: #fdeceb; font-weight: 700; }
.remit { margin-top: 10mm; border-top: 1px solid #ddd; padding-top: 5mm;
font-size: 8.5pt; break-inside: avoid; }
</style>
</head>
<body>
<header>
<div>
<h1>Statement of account</h1>
<address style="font-style:normal;margin-top:2mm">
Northgate Supply Co. · 18 Wharf Road, Bristol BS1 4RN
</address>
</div>
<div class="period">
<strong>Calder Interiors Ltd</strong><br>
Account NG-4471<br>
Period 01 to 31 May 2026<br>
Issued 01 June 2026
</div>
</header>
<div class="summary">
<div><div class="k">Opening balance</div><div class="v">4,102.40</div></div>
<div><div class="k">Invoiced</div><div class="v">2,913.60</div></div>
<div><div class="k">Paid</div><div class="v">-4,102.40</div></div>
<div><div class="k">Closing balance</div><div class="v">2,665.20</div></div>
</div>
<h2>Transactions this period</h2>
<table>
<thead>
<tr><th>Date</th><th>Type</th><th>Reference</th>
<th class="num">Charge</th><th class="num">Payment</th><th class="num">Balance</th></tr>
</thead>
<tbody>
<tr><td>01 May</td><td>Balance b/f</td><td></td>
<td class="num"></td><td class="num"></td><td class="num">4,102.40</td></tr>
<tr><td>08 May</td><td>Payment</td><td>BACS 88104</td>
<td class="num"></td><td class="num">4,102.40</td><td class="num">0.00</td></tr>
<tr><td>14 May</td><td>Invoice</td><td>INV-2026-0184</td>
<td class="num">2,913.60</td><td class="num"></td><td class="num">2,913.60</td></tr>
<tr><td>21 May</td><td>Credit note</td><td>CN-2026-0043</td>
<td class="num">-248.40</td><td class="num"></td><td class="num">2,665.20</td></tr>
</tbody>
<tfoot>
<tr><td colspan="5">Closing balance</td><td class="num">2,665.20</td></tr>
</tfoot>
</table>
<section class="aging">
<h2>Aged analysis</h2>
<table>
<tr><th>Account</th><th>Current</th><th>1 to 30 days</th><th>31 to 60</th>
<th>61 to 90</th><th>Over 90</th><th>Total</th></tr>
<tr>
<td>NG-4471</td><td>2,665.20</td><td>0.00</td><td>0.00</td>
<td>0.00</td><td class="overdue">0.00</td><td>2,665.20</td>
</tr>
</table>
</section>
<section class="remit">
<strong>How to pay.</strong> Bank transfer to Northgate Supply Co., sort code
20-14-88, account 41338207, quoting account number NG-4471. If a payment covers
more than one invoice, send a remittance advice to ar@northgate.example so the
allocation is not guessed. Queries within seven days of this statement, please.
</section>
</body>
</html>The layout decision worth understanding
The four summary tiles at the top exist so the document answers its own question in the first two centimetres. Everything below is evidence for the closing balance. The aged analysis carries break-inside: avoid because an aging table split across a page is worse than useless.
The mistake people make adapting it
Sending a statement that includes invoices raised after the period end date. The closing balance then does not match the transaction list, the customer queries it, and the query costs more than the statement was worth.
Rendering it
The page box is declared in the template's own CSS as A4, so the size travels with the markup rather than living in the code that calls the renderer. A4 is also one of the six named formats the API accepts, so it can be passed in the request options instead if that suits your setup better.
curl -X POST https://api.pdfpipe.xyz/v1/pdf \
-H "Authorization: Bearer $PDFPIPE_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<!doctype html>...",
"options": { "format": "A4" }
}' \
--output statement-of-account.pdfFrequently asked
Can I use this template commercially?
Yes. Copy it, change it, ship it. It is sample markup written to be taken, and the sample data in it is invented, so replace the names and the numbers before anyone sees it.
Why is the page size in the CSS rather than in the API call?
Because a statement of account is a A4 document, and that is a property of the document rather than of the code that renders it. Declaring it in the page box means the size cannot be lost between the template and the call, which is the usual way a document ends up on the wrong paper.
Will it look the same in every renderer?
The layout will, because it uses ordinary CSS. The page break behaviour will not: break-inside and break-after are implemented differently by browser engines and by dedicated print engines, and some of the paged media specification is not implemented in browser renders at all. That difference is documented per property in the paged CSS reference.
Other templates
Documents from the same part of a business, and one from each of the other groups.
HTML invoice template with CSS
The standard commercial invoice: a demand for payment with line items, a tax figure, and terms that say when the money is due.
EU VAT invoice HTML template
A cross-border invoice inside the EU, with both parties' VAT identification numbers, tax broken out per rate, and a reverse charge statement.
GST tax invoice HTML template for India
An Indian tax invoice with GSTIN for both parties, HSN codes per line, and tax split into CGST and SGST or charged as IGST depending on the place of supply.
Commercial invoice template for customs
The customs document that travels with an international shipment: what the goods are, what they are worth, where they were made, and under what terms they move.
Payment receipt HTML template
Proof that a payment was made: the amount, the payment reference, the method, and what it was for.
Quotation HTML template with CSS
A priced proposal with a validity date, a scope, optional items kept out of the headline total, and somewhere to sign.
Packing slip HTML template
The document that goes in the box: what was ordered, what was actually shipped, and no prices at all.
Every template
The full set, grouped by what part of a business produces the document.
A4 page size for PDF generation
A4 is the default page for business documents everywhere except North America, and the default this API uses when no format is given.
Paste the template into the playground and get the PDF back. No signup, no key.