Examples

Invoice Template

A complete recipe for building a professional invoice PDF - line items, totals, and formatted billing parties - using Section, Layout Group, Table, and Template Settings together.

What You'll Build

A professional invoice document that includes:

  • A branded header with invoice number and issue/due dates
  • Side-by-side "Bill From" and "Bill To" blocks
  • A line items table with descriptions, quantities, unit prices, and a totals summary
  • Locale-aware date and currency formatting applied consistently across the whole document

This example shows how four controls work together as a system rather than as isolated pieces.


Controls Used

ControlRole in This Template
templateSettingsSets locale, date format, and currency symbol once - applies everywhere
sectionGroups the invoice header and wraps the totals block
layout_groupPlaces "Bill From" and "Bill To" columns side by side with independent titles
tableRenders line items bound to the lineItems array with a subtotal aggregate

Payload Shape

Every VaultPDF payload merges template instructions with your runtime data at the top level. For this invoice:

{
  "templateId": "invoice-standard",
  "templateSettings": { },
  "theme":            { },
  "layoutSchema":     { },

  "invoiceNumber": "INV-2026-0042",
  "issueDate":     "2026-03-18",
  "dueDate":       "2026-04-18",
  "from":          { },
  "to":            { },
  "lineItems":     [ ],
  "subtotal":      0,
  "taxAmount":     0,
  "totalDue":      0
}

The rendering engine reads templateSettings, theme, and layoutSchema as instructions. Everything else (invoiceNumber, from, lineItems, etc.) is document data that controls can bind to.


Step 1 - Template Settings

Set locale and date format here once so that every date field and every currency column in the document picks them up automatically.

"templateSettings": {
  "locale":     "en-US",
  "dateFormat": "MMM D, YYYY",
  "timezone":   "America/New_York"
}

Why set these globally?

Setting locale and dateFormat in templateSettings means you never have to repeat them per column or per field. Every "format": "date" and "format": "currency" in the document inherits these values. Changing the locale once here switches the whole document.


Step 2 - Theme

Setting theme.primary here propagates to table header backgrounds, section accent pipes, and title icons throughout the document - no per-control color overrides needed.

"theme": {
  "primary": "#1a56db"
}

Step 3 - Billing Parties (Layout Group)

The "Bill From" and "Bill To" blocks need to sit side by side, and each needs its own titled section with an accent pipe. A layout_group is the right choice here.

{
  "type": "layout_group",
  "props": {
    "columns": [
      { "width": "star" },
      { "width": "star" }
    ],
    "columnGap": 16,
    "verticalAlign": "top"
  }
}

Each column holds a section with its own title and icon. Sections placed directly inside a layout_group column render at full strength - the accent pipe and title icon behave exactly as they would at the top level.

layout_group vs section layout: 2

Use layout_group when each column needs its own independently titled section block (like Bill From / Bill To). Use a section with "layout": "2" when you want one titled section whose child fields split across two columns - for example, a two-column address form inside a single "Contact Details" header.


Step 4 - Line Items (Table)

The table binds to the lineItems array via dataPath. Each column maps a field key to a header label. Setting "format": "currency" on the price columns means the locale from templateSettings applies automatically - no extra config.

{
  "type": "table",
  "props": {
    "title":     "Line Items",
    "titleIcon": "list",
    "dataPath":  "lineItems",
    "columns": [
      { "key": "description", "label": "Description", "width": "*",  "align": "left" },
      { "key": "qty",         "label": "Qty",          "width": 45,  "align": "right" },
      { "key": "unitPrice",   "label": "Unit Price",   "width": 85,  "align": "right", "format": "currency" },
      { "key": "amount",      "label": "Amount",       "width": 85,  "align": "right", "format": "currency" }
    ],
    "aggregates": [
      { "op": "sum", "key": "amount", "label": "Subtotal" }
    ]
  }
}

Sample lineItems array:

"lineItems": [
  { "description": "Consulting - March 2026", "qty": 8, "unitPrice": 250.00, "amount": 2000.00 },
  { "description": "Platform Setup",          "qty": 1, "unitPrice": 850.00, "amount": 850.00  },
  { "description": "Documentation Review",   "qty": 4, "unitPrice": 150.00, "amount": 600.00  }
]

VaultPDF does not calculate totals

The amount field must arrive pre-calculated in the payload (qty * unitPrice computed by your system). The aggregates block sums the amount column across all rows - it does not evaluate expressions between columns.


Step 5 - Totals Block

The totals (subtotal, tax, total due) live below the table in a narrow right-aligned column. The simplest approach is a section with "layout": "2" where the left child is a spacer and the right child is the fields block - this pushes the totals to the right half of the page.

Why not another layout_group?

The totals block does not need independent titled sections - it is one logical unit that happens to need right-alignment. A section with layout: "2" + a spacer is lighter and avoids unnecessary nesting.

The totals data fields in the payload:

"subtotal":   3450.00,
"taxAmount":   276.00,
"totalDue":   3726.00

Complete Payload

The full assembled payload ready to send to the render API or paste into the Playground:

{
  "templateId": "invoice-standard",
  "templateSettings": {
    "locale":     "en-US",
    "dateFormat": "MMM D, YYYY",
    "timezone":   "America/New_York"
  },
  "theme": {
    "primary": "#1a56db"
  },
  "invoiceNumber": "INV-2026-0042",
  "issueDate":     "2026-03-18",
  "dueDate":       "2026-04-18",
  "from": {
    "name":    "Acme Corp",
    "address": "123 Main Street, New York, NY 10001",
    "email":   "[email protected]",
    "phone":   "+1 212 555 0100"
  },
  "to": {
    "name":    "Globex Inc",
    "address": "456 Market Ave, Boston, MA 02101",
    "email":   "[email protected]",
    "phone":   "+1 617 555 0200"
  },
  "lineItems": [
    { "description": "Consulting - March 2026", "qty": 8, "unitPrice": 250.00, "amount": 2000.00 },
    { "description": "Platform Setup",          "qty": 1, "unitPrice": 850.00, "amount": 850.00  },
    { "description": "Documentation Review",   "qty": 4, "unitPrice": 150.00, "amount": 600.00  }
  ],
  "subtotal":  3450.00,
  "taxAmount":  276.00,
  "totalDue":  3726.00
}

The layoutSchema is template-authored

The layoutSchema (the section/layout_group/table wiring) lives inside the .vpdf template file managed in the Designer - not in every API call. The payload above represents the runtime data you POST alongside the template. The rendering engine merges them at request time.


Try It in the Playground

Open the VaultPDF Playground and select the Invoice template to see this pattern in action with a live preview. You can modify the lineItems array or swap theme.primary and regenerate the PDF instantly without any deployment.


Design Decisions Summary

DecisionWhy
layout_group for billing partiesEach party needs an independent titled section block
section layout: "2" + spacer for totalsOne logical unit needing right-alignment - no independent titles
templateSettings.locale at the topCurrency and date format applied once, consistent across all columns
aggregates on the tableSubtotal is a display-level sum - computed by the renderer from the amount column
Pre-calculated amount per rowBusiness logic (qty x price) stays in your system, not in the template

Next Steps

On this page