Quire PDFDocumentation

Templates and the generic layout

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.

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

The 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 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:

{
  "order_id": "ORD-88213",
  "date": "2 Oct 2026",
  "currency": "€",
  "to": { "name": "Lena Fischer", "email": "[email protected]", "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.

View this page as Markdown