invoice
An example template.
Browse this template in the repository →
The PDFs below are rendered live by PDF Server from the shipped examples.
overdue
paid
Inputs
Render data
overdue
examples/overdue.json
{
"seller": {
"name": "PDF Server",
"address": "1 Example Plaza, Suite 100, Anytown, EX 00000",
},
"customer": {
"name": "Globex Corporation",
"address": "456 Sample Boulevard, Testville, EX 22222"
},
"invoice": {
"number": "INV-2026-0152",
"status": "due",
"currency": "USD",
"issuedAt": "2026-05-20T09:00:00Z",
"dueAt": "2026-06-03T09:00:00Z",
"items": [
{
"name": "Example Service (monthly)",
"detail": "Placeholder subscription — tier 2",
"quantity": 8,
"unitPrice": "$62.00",
"amount": "$496.00"
},
{
"name": "Demo Add-on",
"detail": "Sample line item, 2-color print",
"quantity": 24,
"unitPrice": "$7.25",
"amount": "$174.00"
}
],
"subtotal": "$670.00",
"taxRate": "9.25%",
"tax": "$61.98",
"total": "$731.98",
"notes": "Payment is **past due**. Please remit within 5 business days to avoid a 1.5% late fee.\n\nQuestions? Reply to [email protected]."
}
}
paid
examples/paid.json
{
"seller": {
"name": "PDF Server",
"address": "1 Example Plaza, Suite 100, Anytown, EX 00000",
},
"customer": {
"name": "Acme Corporation",
"address": "123 Example Street, Sampleton, EX 11111",
},
"invoice": {
"number": "INV-2026-0148",
"status": "paid",
"currency": "USD",
"issuedAt": "2026-06-01T09:00:00Z",
"dueAt": "2026-06-15T09:00:00Z",
"items": [
{
"name": "Sample Widget (large)",
"detail": "Placeholder line item — case of 500",
"quantity": 4,
"unitPrice": "$210.00",
"amount": "$840.00"
},
{
"name": "Example Gadget",
"detail": "Demonstration part, self-inking",
"quantity": 6,
"unitPrice": "$18.50",
"amount": "$111.00"
},
{
"name": "Shipping & handling",
"quantity": 1,
"unitPrice": "$45.00",
"amount": "$45.00"
}
],
"subtotal": "$996.00",
"taxRate": "8.5%",
"tax": "$84.66",
"total": "$1,080.66",
"notes": "Paid in full on **June 9, 2026** via ACH transfer.\n\nThank you for trying the PDF Server demo!"
}
}
Data schema
schema.json
{
"title": "Invoice",
"type": "object",
"required": ["seller", "customer", "invoice"],
"additionalProperties": false,
"properties": {
"seller": {
"type": "object",
"required": ["name", "address", "email"],
"additionalProperties": false,
"properties": {
"name": { "type": "string" },
"address": { "type": "string" },
"email": { "type": "string", "format": "email" }
}
},
"customer": {
"type": "object",
"required": ["name", "address"],
"additionalProperties": false,
"properties": {
"name": { "type": "string" },
"address": { "type": "string" },
"email": { "type": "string", "format": "email" }
}
},
"invoice": {
"type": "object",
"required": [
"number",
"status",
"currency",
"issuedAt",
"dueAt",
"items",
"subtotal",
"taxRate",
"tax",
"total"
],
"additionalProperties": false,
"properties": {
"number": { "type": "string" },
"status": { "enum": ["paid", "due"] },
"currency": { "type": "string", "description": "ISO 4217 code shown next to the total." },
"issuedAt": { "type": "string", "format": "date-time" },
"dueAt": { "type": "string", "format": "date-time" },
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["name", "quantity", "unitPrice", "amount"],
"additionalProperties": false,
"properties": {
"name": { "type": "string" },
"detail": { "type": "string" },
"quantity": { "type": "number" },
"unitPrice": { "type": "string", "description": "Pre-formatted, e.g. \"$1,200.00\"." },
"amount": { "type": "string", "description": "Pre-formatted line total." }
}
}
},
"subtotal": { "type": "string" },
"taxRate": { "type": "string", "description": "Human label, e.g. \"8.5%\"." },
"tax": { "type": "string" },
"total": { "type": "string" },
"notes": { "type": "string", "description": "Markdown; rendered with the markdown helper." }
}
}
}
}
PDF parameters
params.json
{
"pdf": {
"printBackground": true,
"paperWidth": 8.27,
"paperHeight": 11.69,
"marginTop": 0.6,
"marginBottom": 0.6,
"marginLeft": 0.6,
"marginRight": 0.6,
"waitLifecycleEvent": "load"
}
}
Template
footer.html
<!-- Running footer. Header falls back to the default (kept empty via a thin blank). -->
<div style="width: 100%; font-family: Arial, sans-serif; font-size: 9px; color: #6b7280; padding: 0 16mm; display: flex; justify-content: space-between;">
<span>PDF Server — Thank you for your business</span>
<span>Page <span class="pageNumber"></span> of <span class="totalPages"></span></span>
</div>
header.html
<!-- Empty running header: reserves no visible ink but overrides the built-in default. -->
<div></div>
template.handlebars
Partials
partials/line-item.handlebars
Assets
assets/logo.svg
<svg xmlns="http://www.w3.org/2000/svg" width="210" height="40" viewBox="0 0 210 40" role="img" aria-label="PDF Server">
<!-- PDF Server brand mark (from logo-light.svg): a sheet of paper with
angle-bracket eyes and a curly-brace mustache. Brand colors: ink
#2C2C2A, surface #F1EFE8, primary #D85A30. -->
<g transform="translate(0 4)">
<path d="M4 5 q0-2 2-2 h13 l7 7 v17 q0 2-2 2 h-18 q-2 0-2-2 z" fill="#2C2C2A"/>
<path d="M19 3 l7 7 h-4 q-3 0-3-3 z" fill="#D85A30"/>
<path d="M12 11.5 l-2 2 l2 2" fill="none" stroke="#F1EFE8" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18 11.5 l2 2 l-2 2" fill="none" stroke="#F1EFE8" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 18.5 q0 2.5 2.5 2.5 h2 q2.5 0 2.5 2.5 q0-2.5 2.5-2.5 h2 q2.5 0 2.5-2.5"
fill="none" stroke="#F1EFE8" stroke-width="2.2"
stroke-linecap="round" stroke-linejoin="round"/>
</g>
<text x="44" y="27" font-family="Helvetica Neue, Arial, sans-serif" font-size="20" font-weight="700">
<tspan fill="#D85A30">PDF</tspan><tspan fill="#2C2C2A"> Server</tspan>
</text>
</svg>
assets/styles.css
/* PDF Server brand palette (from logo-auto.svg's brand-colors block).
Edit --accent live during a demo to re-brand the whole document. */
:root {
--accent: #d85a30; /* --brand-primary */
--surface: #f1efe8; /* --brand-surface, for the notes panel */
--ink: #2c2c2a; /* --brand-ink */
--muted: #6f6d66;
--line: #e3e1d9;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "Helvetica Neue", Arial, sans-serif;
color: var(--ink);
font-size: 12px;
line-height: 1.5;
}
h1 {
margin: 0;
font-size: 30px;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--accent);
}
h2 {
margin: 0 0 6px;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
}
/* Masthead: brand on the left, document metadata on the right. */
.masthead {
display: flex;
justify-content: space-between;
align-items: flex-start;
border-bottom: 3px solid var(--accent);
padding-bottom: 16px;
}
.logo {
height: 44px;
margin-bottom: 10px;
}
.seller {
display: flex;
flex-direction: column;
}
.seller strong {
font-size: 14px;
}
.seller span {
color: var(--muted);
}
.doc {
text-align: right;
}
.badge {
display: inline-block;
margin: 8px 0;
padding: 3px 10px;
border-radius: 999px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.badge--paid {
background: #e6f4ea;
color: #1f7a45;
}
.badge--due {
background: #fdecec;
color: #c0392b;
}
.doc-meta {
display: grid;
grid-template-columns: auto auto;
gap: 2px 12px;
margin: 0;
justify-content: end;
}
.doc-meta dt {
color: var(--muted);
text-align: right;
}
.doc-meta dd {
margin: 0;
font-weight: 600;
}
.bill-to {
margin: 24px 0;
}
/* Items table */
.items {
width: 100%;
border-collapse: collapse;
margin-top: 8px;
}
.items thead th {
background: var(--accent);
color: #fff;
text-align: left;
padding: 8px 10px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.items tbody td {
padding: 10px;
border-bottom: 1px solid var(--line);
vertical-align: top;
}
.items .detail {
display: block;
color: var(--muted);
font-size: 11px;
}
.col-qty {
text-align: center;
white-space: nowrap;
}
.col-num {
text-align: right;
white-space: nowrap;
}
.items tfoot td {
padding: 6px 10px;
text-align: right;
color: var(--muted);
}
.items tfoot .grand-total td {
padding-top: 12px;
font-size: 15px;
font-weight: 700;
color: var(--ink);
border-top: 2px solid var(--accent);
}
.notes {
margin-top: 28px;
padding: 14px 16px;
background: var(--surface);
border-radius: 6px;
}
.notes p {
margin: 0 0 6px;
}