# Templates and the generic layout

> The six gallery templates, how Quire auto-detects one from your data, and the exact rules the generic layout uses to turn any JSON into a document.

Quire has seven built-in layouts: six gallery templates for common business documents, and a generic `document` layout that renders any JSON object. You don't have to pick one. Send your data and Quire chooses.

## The gallery

| Template | What it renders |
|---|---|
| [`invoice`](/templates/invoice) | Line items, discount, tax, amount-due panel and payment details. Multi-page with a repeating table header. |
| [`receipt`](/templates/receipt) | Proof of payment: amount-paid panel with a PAID badge, payment method, line items and a support note. |
| [`quote`](/templates/quote) | Quote or estimate with line items grouped into sections, subtotals, terms and a client acceptance block. |
| [`credit-note`](/templates/credit-note) | Credit against an earlier invoice: reason, credited items, tax, and how the credit is applied. |
| [`statement`](/templates/statement) | Statement of account for a period: opening and closing balances, activity with a running balance, optional aging. |
| [`certificate`](/templates/certificate) | Landscape completion or achievement certificate with signatures, seal and credential ID. Always one page. |

The [`document`](/templates/document) layout is the seventh. Each gallery page lists the template's fields, its JSON Schema and a sample. From the terminal, `npx quirepdf-cli templates invoice` shows the same.

Gallery templates do the arithmetic for you: send `qty` and `unit_price`, plus a tax or discount `rate` such as `0.18`, and the template computes subtotals and totals. The statement template computes the running balance from `opening_balance`.

## How auto-detection works

When you don't name a template, Quire checks your data against each gallery template's JSON Schema:

1. If the data passes one or more schemas, the **most specific** template wins: the one with the most required top-level fields. Ties go to the name that sorts first.
2. If nothing passes, Quire uses the generic `document` layout.
3. If a template almost matched (two problems or fewer), the response says so in `x-template-hint`, for example `invoice (seller is required)`. Add the missing field and the next render uses the invoice template.

Detection is a schema check, nothing more: it's deterministic and costs nothing extra. The `document` layout has no required fields, so it's never detected; it's only the fallback, or used when you ask for it with `?template=document`.

To check which template your data resolves to without rendering, call [`/v1/validate`](/docs/render-api#validate-free) with no `template`. It's free.

Naming a template (`?template=invoice`) switches off detection. Your data must then pass that template's schema, or you get `422 invalid_data` with every problem listed.

## The generic layout

Here is an order record with no template:

```json
{
  "order_id": "ORD-88213",
  "date": "2 Oct 2026",
  "currency": "€",
  "to": { "name": "Lena Fischer", "email": "lena@example.de", "address": ["Torstraße 12", "10119 Berlin"] },
  "status": "Shipped",
  "carrier": "DHL Paket",
  "gift_message": "Happy birthday, Jonas! I hope these keep you warm on the trail this winter. Love, Lena and the whole Fischer family.",
  "lines": [
    { "sku": "TS-BLK-M", "name": "Organic tee, black, M", "qty": 2, "price": 29.0, "total": 58.0 },
    { "sku": "SK-GRY-L", "name": "Wool socks, grey, L", "qty": 3, "price": 9.5, "total": 28.5 }
  ],
  "totals": { "subtotal": 86.5, "shipping": 4.9, "total": 91.4 },
  "tags": ["gift", "priority"]
}
```

The result is a one-page A4 document:

- **Header:** the title "Order" with "ORD-88213" on the right, inferred from `order_id`.
- **To** block: Lena Fischer, her two address lines and email.
- **Details** panel: Date 2 Oct 2026, Status Shipped, Carrier DHL Paket.
- **Gift message:** a titled paragraph, because the string is longer than 80 characters.
- **Lines:** a table with SKU, Name, Qty, Price and Total columns. The numeric columns are right-aligned, and Price and Total show as `€29.00`, `€58.00`.
- **Totals:** a key/value grid. Subtotal and Total are money (`€86.50`, `€91.40`); Shipping prints as `4.9`, because "shipping" isn't a money word.
- **Tags:** a bullet list.
- **Footer:** "Order · ORD-88213" and "1 of 1".

### Reserved keys

These top-level keys shape the header and page instead of becoming sections:

| Key | Type | Effect |
|---|---|---|
| `title` | string | Large heading. Default "Document". |
| `subtitle` | string | A muted line under the title |
| `number` | string or number | Shown at the top right and in the footer |
| `date` | string | First row of the Details panel. Send it pre-formatted. |
| `from` | string or object | The sender: `name` and `email` appear in the header |
| `to` | string or object | A "To" block: `name`, `company`, `address` (a string or an array of lines), `email` |
| `accent` | `#rrggbb` | Brand colour for the top rule and marks |
| `currency` | up to 4 characters | Turns on money formatting, e.g. `"$"`, `"€"`, `"CHF "` |
| `notes` | string | A Notes block at the end |
| `footer` | string | Replaces the default footer text (title · number) |

`accent` must be a six-digit hex colour, or the request fails with `accent has an invalid format`.

**No title?** Quire looks for the first top-level key ending in `_id`, `_number`, `_no`, `_ref` or `_reference` with a string or integer value. `invoice_no: "A-17"` becomes the title "Invoice" and the number "A-17", and that key isn't repeated in the body.

### Every other key, in order

Each remaining top-level key becomes part of the body, in the order the keys appear in your JSON:

| Value | Rendered as |
|---|---|
| Short scalar (string of 80 characters or fewer, number, boolean, null) | A row in the **Details** panel next to the To block |
| String longer than 80 characters | A titled paragraph section |
| Object | A titled section: its scalar fields in a key/value grid, its nested objects and arrays as sub-sections (up to four levels deep) |
| Array of objects | A table. Columns are every key seen, in first-seen order. Numeric columns are right-aligned. The header row repeats on each page. |
| Array of scalars | A bullet list |
| Mixed array | A list of rendered values |
| Empty array | "None" |

Inside a table cell, a nested object or array is summarised on one line, like `City: Berlin, Zip: 10119`. Tables with more than six columns use smaller text.

### Formatting

- **Keys** are humanised: `parts_used` becomes "Parts used".
- **Numbers** get thousands separators (`12,000`) and at most two decimals, without trailing zeros (`55.2`).
- **Money:** when `currency` is set, a number whose key contains amount, price, total, cost, fee, balance, subtotal, tax, rate, charge, payment, paid, discount, refund, salary or budget is shown as money with two decimals (`€1,284.37`). A `rate` below 1 stays a plain number, so a `0.18` tax rate isn't shown as `€0.18`.
- **Booleans** print as Yes or No. **Null** prints as "—".
- **No automatic sums.** The generic layout never adds up a column or invents a total. If you want a total, send it.

## Tips for shaping your JSON

- **Key order is section order.** Put the most important section first. Most JSON libraries keep insertion order, including `JSON.stringify` and Python's `json.dumps`.
- **Set `currency`** whenever the data has money in it, and name money keys with the words above: `shipping_fee`, not `shipping`.
- **Use `title`, `from` and `to`** for a proper letterhead. Without them you get a plain heading.
- **Pre-format dates and labels.** Quire prints strings as given, so send `"2 Oct 2026"`, not a timestamp.
- **Use arrays of objects for anything tabular.** Keep each row's keys the same so the columns line up.
- **Flatten what you don't need.** Internal IDs and deeply nested metadata become sections too; drop them before sending.
- **Read the hint.** If `x-template-hint` names a template, a field or two more gets you a purpose-built layout with computed totals.