Skip to content
PPDFInvoiceAPI
DOCS/ Template examples

CORE

Template examples

Three production-ready starting points — a clean invoice, a payment receipt and an account statement. Copy one, swap in your data, render.

How to use these

Each example is a self-contained HTML document: inline<style> only and no JavaScript (JS is disabled at render time, so everything is plain HTML + CSS). They're sized to print cleanly to A4 with sensible margins.

There are two ways to render one:

  • As a stored template — paste the*.html tab into Dashboard → Templates, then render by its tpl_… id, passing the matching data object each call (see Templates).
  • Inline — substitute your own values and POST the finished markup as the html field to /v1/render (see Rendering HTML).

About repeating rows. The template merge supports{{ value }}, raw {{{ value }}} and dotted paths — but no loops. So for line items or ledger rows you build the <tr> markup in your own code and drop it in with a raw {{{ lineItemsHtml }}} placeholder. The data.json tab shows the exact shape, and the invoice has a small Node helper for building the rows. (Escape any user-supplied text you put into a raw placeholder.)

Treat these as a starting point. The invoice is already brandable from data: its logo comes from{{ from.logoUrl }} (an https:// or data: image) and every accent colour reads a single--brand CSS variable fed by {{ brandColor }} — so one stored template renders for every client in their own look. See Make it your brand for logo, font and colour recipes.

Invoice

A clean, VAT-ready invoice: a logo header, from / bill-to blocks, a line-items table, a subtotal → VAT → total summary, and a payment-terms note. The logo and the accent colour are both driven from data — swap from.logoUrl and brandColor and the whole document re-themes.

<!doctype html>
<!-- One brand colour, fed from your data, drives every accent via var(--brand). -->
<html lang="en" style="--brand: {{brandColor}}">
  <head>
    <meta charset="utf-8" />
    <style>
      * { box-sizing: border-box; }
      html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
      body {
        margin: 0;
        font-family: "Helvetica Neue", Arial, sans-serif;
        color: #1a1a1a;
        font-size: 13px;
        line-height: 1.5;
      }
      .sheet { padding: 48px 56px; }
      .top {
        display: flex;
        justify-content: space-between;
        align-items: flex-start;
        border-bottom: 2px solid var(--brand);
        padding-bottom: 24px;
      }
      .brand img { height: 40px; display: block; }
      .brand small { display: block; font-weight: 400; font-size: 12px; color: #6b7280; margin-top: 6px; }
      .doc-meta { text-align: right; }
      .doc-meta h1 { margin: 0; font-size: 26px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--brand); }
      .doc-meta .num { font-size: 14px; font-weight: 600; margin-top: 4px; }
      .doc-meta .dates { font-size: 12px; color: #6b7280; margin-top: 6px; }
      .parties { display: flex; gap: 48px; margin-top: 32px; }
      .parties .label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: #9ca3af; margin-bottom: 6px; }
      .parties strong { display: block; font-size: 14px; }
      .parties .addr { color: #4b5563; white-space: pre-line; }
      table { width: 100%; border-collapse: collapse; margin-top: 36px; }
      thead th {
        text-align: left;
        font-size: 10px;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        color: #6b7280;
        border-bottom: 1px solid #e5e7eb;
        padding: 0 8px 8px;
      }
      thead th.r { text-align: right; }
      tbody td { padding: 12px 8px; border-bottom: 1px solid #f0f1f3; vertical-align: top; }
      tbody td.r { text-align: right; }
      tbody td.desc { font-weight: 500; }
      tr { break-inside: avoid; }
      .totals { width: 280px; margin-left: auto; margin-top: 20px; }
      .totals .row { display: flex; justify-content: space-between; padding: 6px 8px; color: #4b5563; }
      .totals .grand {
        border-top: 2px solid var(--brand);
        margin-top: 6px;
        padding-top: 12px;
        font-size: 17px;
        font-weight: 700;
        color: var(--brand);
      }
      .notes { margin-top: 40px; font-size: 12px; color: #4b5563; }
      .notes .label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: #9ca3af; margin-bottom: 4px; }
      footer { margin-top: 48px; border-top: 1px solid #e5e7eb; padding-top: 14px; font-size: 11px; color: #9ca3af; text-align: center; }
    </style>
  </head>
  <body>
    <div class="sheet">
      <div class="top">
        <div class="brand">
          <!-- Your logo, fed from data (https:// URL or data: base64). -->
          <img src="{{from.logoUrl}}" alt="{{from.name}}" />
          <small>{{from.tagline}}</small>
        </div>
        <div class="doc-meta">
          <h1>Invoice</h1>
          <div class="num">{{number}}</div>
          <div class="dates">
            Issued {{issued}}<br />
            Due {{due}}
          </div>
        </div>
      </div>

      <div class="parties">
        <div>
          <div class="label">From</div>
          <strong>{{from.name}}</strong>
          <div class="addr">{{from.address}}</div>
          <div class="addr">VAT {{from.vat}}</div>
        </div>
        <div>
          <div class="label">Bill to</div>
          <strong>{{billTo.name}}</strong>
          <div class="addr">{{billTo.address}}</div>
          <div class="addr">{{billTo.email}}</div>
        </div>
      </div>

      <table>
        <thead>
          <tr>
            <th>Description</th>
            <th class="r">Qty</th>
            <th class="r">Unit</th>
            <th class="r">Amount</th>
          </tr>
        </thead>
        <!-- rows are built by your code (see data.json) and dropped in raw -->
        <tbody>{{{ lineItemsHtml }}}</tbody>
      </table>

      <div class="totals">
        <div class="row"><span>Subtotal</span><span>{{subtotal}}</span></div>
        <div class="row"><span>VAT ({{vatRate}})</span><span>{{vat}}</span></div>
        <div class="row grand"><span>Total due</span><span>{{total}}</span></div>
      </div>

      <div class="notes">
        <div class="label">Payment</div>
        {{paymentTerms}}
      </div>

      <footer>{{from.name}} · {{from.email}} · {{from.website}}</footer>
    </div>
  </body>
</html>

Receipt

A compact, narrow payment receipt with a Paid badge, the charged amount, the card used and an itemised breakdown — ideal for post-payment confirmation emails.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <style>
      * { box-sizing: border-box; }
      html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
      body {
        margin: 0;
        font-family: "Helvetica Neue", Arial, sans-serif;
        color: #1a1a1a;
        font-size: 13px;
      }
      .receipt { max-width: 420px; margin: 0 auto; padding: 40px 32px; }
      .logo { height: 32px; margin-bottom: 20px; }
      .paid {
        display: inline-block;
        font-size: 11px;
        font-weight: 700;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: #047857;
        background: #ecfdf5;
        border: 1px solid #a7f3d0;
        border-radius: 999px;
        padding: 4px 12px;
      }
      h1 { font-size: 18px; margin: 16px 0 2px; }
      .sub { color: #6b7280; font-size: 12px; }
      .amount { font-size: 34px; font-weight: 700; letter-spacing: -0.02em; margin: 24px 0 4px; }
      .meta { margin-top: 28px; border-top: 1px solid #e5e7eb; padding-top: 16px; }
      .meta .row { display: flex; justify-content: space-between; padding: 5px 0; }
      .meta .row span:first-child { color: #6b7280; }
      .meta .row span:last-child { font-weight: 500; }
      .items { margin-top: 20px; }
      .items .line { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f0f1f3; }
      .items .line .name small { display: block; color: #9ca3af; font-size: 11px; }
      .total { display: flex; justify-content: space-between; padding: 14px 0 0; font-size: 15px; font-weight: 700; }
      footer { margin-top: 32px; text-align: center; color: #9ca3af; font-size: 11px; line-height: 1.6; }
    </style>
  </head>
  <body>
    <div class="receipt">
      <img class="logo" src="{{merchant.logoUrl}}" alt="{{merchant.name}}" />
      <span class="paid">Paid</span>
      <h1>Receipt from {{merchant.name}}</h1>
      <div class="sub">{{receiptNumber}} · {{paidAt}}</div>

      <div class="amount">{{total}}</div>
      <div class="sub">Charged to {{payment.brand}} ending {{payment.last4}}</div>

      <div class="items">
        {{{ lineItemsHtml }}}
        <div class="total"><span>Total</span><span>{{total}}</span></div>
      </div>

      <div class="meta">
        <div class="row"><span>Billed to</span><span>{{customer.name}}</span></div>
        <div class="row"><span>Email</span><span>{{customer.email}}</span></div>
        <div class="row"><span>Payment method</span><span>{{payment.brand}} •••• {{payment.last4}}</span></div>
        <div class="row"><span>Date</span><span>{{paidAt}}</span></div>
      </div>

      <footer>
        {{merchant.name}} · {{merchant.address}}<br />
        Questions? {{merchant.email}}
      </footer>
    </div>
  </body>
</html>

Statement

An account statement: a dated ledger of charges and credits over a period with an opening balance, totals and a closing balance due.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <style>
      * { box-sizing: border-box; }
      html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
      body { margin: 0; font-family: "Helvetica Neue", Arial, sans-serif; color: #1a1a1a; font-size: 13px; }
      .sheet { padding: 48px 56px; }
      header { display: flex; justify-content: space-between; align-items: flex-end; border-bottom: 2px solid #1a1a1a; padding-bottom: 20px; }
      header .brand { font-size: 18px; font-weight: 700; }
      header h1 { margin: 0; font-size: 22px; text-transform: uppercase; letter-spacing: 0.04em; color: #6b7280; }
      .for { margin-top: 24px; color: #4b5563; }
      .for strong { color: #1a1a1a; }
      .period { color: #9ca3af; font-size: 12px; margin-top: 2px; }
      table { width: 100%; border-collapse: collapse; margin-top: 28px; }
      thead th { text-align: left; font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding: 0 8px 8px; }
      thead th.r { text-align: right; }
      tbody td { padding: 10px 8px; border-bottom: 1px solid #f0f1f3; }
      tbody td.r { text-align: right; }
      tr { break-inside: avoid; }
      .summary { width: 300px; margin-left: auto; margin-top: 20px; }
      .summary .row { display: flex; justify-content: space-between; padding: 6px 8px; color: #4b5563; }
      .summary .balance { border-top: 2px solid #1a1a1a; margin-top: 6px; padding-top: 12px; font-size: 16px; font-weight: 700; color: #1a1a1a; }
      footer { margin-top: 44px; border-top: 1px solid #e5e7eb; padding-top: 14px; font-size: 11px; color: #9ca3af; text-align: center; }
    </style>
  </head>
  <body>
    <div class="sheet">
      <header>
        <div class="brand">{{company.name}}</div>
        <h1>Statement</h1>
      </header>

      <div class="for">
        <strong>{{customer.name}}</strong> · Account {{customer.account}}
        <div class="period">Statement period {{period}}</div>
      </div>

      <table>
        <thead>
          <tr>
            <th>Date</th>
            <th>Description</th>
            <th class="r">Charges</th>
            <th class="r">Credits</th>
          </tr>
        </thead>
        <tbody>{{{ entriesHtml }}}</tbody>
      </table>

      <div class="summary">
        <div class="row"><span>Opening balance</span><span>{{opening}}</span></div>
        <div class="row"><span>Total charges</span><span>{{totalCharges}}</span></div>
        <div class="row"><span>Total credits</span><span>{{totalCredits}}</span></div>
        <div class="row balance"><span>Balance due</span><span>{{balance}}</span></div>
      </div>

      <footer>{{company.name}} · {{company.email}} · Generated {{generatedAt}}</footer>
    </div>
  </body>
</html>