Guides / Build an invoice

Build an invoice

This is a full template, start to finish: the data shape, the markup, and the call. It mirrors the Example invoice seeded into every new account, so you can follow along there.

1. Decide the data shape

Design the JSON your template will receive. A line-items array drives the table; a couple of nested objects hold the parties.

{
  "number": "INV-2048",
  "date": "18 June 2026",
  "seller": { "name": "PDFglyph Ltd", "vatId": "FR12345678901" },
  "customer": { "name": "Northwind Ltd", "address": "4 Trade St, London" },
  "items": [
    { "description": "Consulting", "qty": 6, "unitPrice": "€150.00", "amount": "€900.00" },
    { "description": "Support", "qty": 1, "unitPrice": "€340.00", "amount": "€340.00" }
  ],
  "subtotal": "€1,240.00",
  "tax": "€248.00",
  "total": "€1,488.00",
  "notes": "Payment due within 30 days."
}

Note that money is already formatted as strings. There are no formatting helpers in templates, so do currency, rounding, and dates in your code before sending. See formatting.

2. Write the template

Inline the CSS, set the page with @page, and pad the body since page margins are zero. The line items come from {{#each items}}, and the notes block is conditional.

<!doctype html>
<html>
  <head>
    <style>
      @page { size: A4; }
      body { font-family: Inter, system-ui, sans-serif; color: #14322a; padding: 18mm; }
      .head { display: flex; justify-content: space-between; align-items: flex-start; }
      h1 { margin: 0; font-size: 28px; }
      table { width: 100%; border-collapse: collapse; margin-top: 24px; }
      th, td { text-align: left; padding: 8px 0; border-bottom: 1px solid #e3ece8; }
      td.amt, th.amt { text-align: right; }
      tr { break-inside: avoid; }
      .totals { margin-top: 16px; width: 240px; margin-left: auto; }
      .totals div { display: flex; justify-content: space-between; padding: 4px 0; }
      .totals .grand { font-weight: 700; border-top: 2px solid #14322a; }
      .notes { margin-top: 28px; color: #4b5b55; }
    </style>
  </head>
  <body>
    <div class="head">
      <div>
        <h1>Invoice {{number}}</h1>
        <p>{{date}}</p>
      </div>
      <div>
        <strong>{{seller.name}}</strong><br />
        VAT {{seller.vatId}}
      </div>
    </div>

    {{#with customer}}
      <p>Billed to <strong>{{name}}</strong><br />{{address}}</p>
    {{/with}}

    <table>
      <thead>
        <tr>
          <th>Description</th>
          <th class="amt">Qty</th>
          <th class="amt">Unit</th>
          <th class="amt">Amount</th>
        </tr>
      </thead>
      <tbody>
        {{#each items}}
          <tr>
            <td>{{description}}</td>
            <td class="amt">{{qty}}</td>
            <td class="amt">{{unitPrice}}</td>
            <td class="amt">{{amount}}</td>
          </tr>
        {{/each}}
      </tbody>
    </table>

    <div class="totals">
      <div><span>Subtotal</span><span>{{subtotal}}</span></div>
      <div><span>Tax</span><span>{{tax}}</span></div>
      <div class="grand"><span>Total</span><span>{{total}}</span></div>
    </div>

    {{#if notes}}
      <p class="notes">{{notes}}</p>
    {{/if}}
  </body>
</html>

3. Preview and publish

Paste the template and the sample data into the editor, preview to confirm the layout, then publish. Publishing freezes this as version 1, which is what the API will render.

4. Generate

Post real data to the template's code. The response body is the invoice PDF.

curl -X POST https://api.pdfglyph.dev/v1/generate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "tpl_invoice_8x2k",
    "data": {
      "number": "INV-2049",
      "date": "19 June 2026",
      "seller": {
        "name": "PDFglyph Ltd",
        "vatId": "FR12345678901"
      },
      "customer": {
        "name": "Acme Co",
        "address": "9 Market Sq, Berlin"
      },
      "items": [
        {
          "description": "Annual license",
          "qty": 1,
          "unitPrice": "€1,200.00",
          "amount": "€1,200.00"
        }
      ],
      "subtotal": "€1,200.00",
      "tax": "€240.00",
      "total": "€1,440.00"
    }
  }' \
  --output document.pdf

The notes field is omitted here, and because it is referenced inside {{#if notes}} it is optional, so the call still succeeds.

Next