Templates
Templates let you separate your document structure from your data. Store a document definition as a template once, then generate personalized PDFs by passing different data each time.
Template workflow
1. Create template (POST /templates)
|
v
2. Store template in database
|
v
3. Generate PDF with data (POST /documents/generate-from-template)
|
v
4. Receive personalized PDF
Step 1: Create a template
A template is a document definition containing $data references and/or source bindings. Save it via the Templates API.
{
"name": "Shipping Invoice",
"content": "{\"document\":{\"styles\":{\"title\":{\"fontSize\":20,\"bold\":true},\"body\":{\"fontSize\":10}},\"contents\":[{\"p\":\"$data.document.title\",\"style\":\"title\"},{\"p\":\"Prepared for $data.client.companyName\"},{\"table\":{\"widths\":[3,1,1],\"rows\":[[{\"p\":\"[b]Item[/b]\"},{\"p\":\"[b]Qty[/b]\"},{\"p\":\"[b]Price[/b]\"}]]},\"source\":\"$data.lineItems\",\"map\":[\"item\",\"qty\",\"price\"]},{\"p\":\"Total: [b]$data.invoice.total[/b]\"}]}}",
"createdBy": "laura.bennett@shipforge.io"
}
The content field holds the JSON document definition as a string. The name field is a human-readable label.
Step 2: Generate with data
Call the generate-from-template endpoint with the template ID and your data object.
curl -X POST https://api.docpayload.com/v1/tenants/{tenantId}/documents/generate-from-template \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"data": {
"document": {
"title": "Invoice #INV-2026-0330"
},
"client": {
"companyName": "Meridian Logistics"
},
"lineItems": [
{ "item": "Express Shipping - Chicago to Toronto", "qty": "3", "price": "$450" },
{ "item": "Customs Clearance Fee", "qty": "1", "price": "$75" },
{ "item": "Insurance - Premium Coverage", "qty": "3", "price": "$120" }
],
"invoice": {
"total": "$645"
}
}
}'
Step 3: Receive the PDF
The response includes a download URL and metadata about the generated document.
What gets resolved
When generating from a template, DocPayload resolves:
| Feature | Description |
|---|---|
| Scalar references | $data.path.to.value in paragraph text |
| Inline formatting refs | $data refs inside [b], [fontcolor], etc. |
| Form field defaults | $data refs in [textfield], [checkbox], [textarea] default values |
| Table source | Populates table rows from object arrays |
| List source | Populates list items from string arrays |
Real-world example: invoice template
This complete example shows a realistic invoice template that combines scalar references, a data-driven table, and a data-driven list.
Template definition
{
"document": {
"metadata": {
"title": "Invoice",
"author": "ShipForge"
},
"pageSetup": {
"size": "A4",
"orientation": "portrait",
"margins": [50, 40, 50, 40]
},
"styles": {
"title": { "fontSize": 20, "bold": true },
"subtitle": { "fontSize": 12, "italic": true, "color": "#555555" },
"total": { "bold": true, "fontSize": 14 },
"footer": { "fontSize": 8, "color": "#999999", "textAlign": "center" }
},
"header": {
"content": [
{ "p": "[image, /assets/shipforge-logo.png, 80|24][tab, 400, 0]INVOICE", "style": "footer" }
],
"separator": { "lineType": "SOLID", "length": 500, "width": 0.5, "color": "#CCCCCC" },
"skipPages": [1]
},
"footer": {
"pageNumber": {
"format": "Page {current} of {total}",
"style": "footer"
}
},
"content": [
{ "p": "$data.document.title", "style": "title" },
{ "p": "Date: $data.document.date", "style": "subtitle" },
{
"separator": {
"lineType": "SOLID",
"length": 500,
"width": 1,
"color": "#000000"
}
},
{
"columns": {
"widths": [1, 1],
"gap": 20,
"rows": [
[
{ "p": "[b]From:[/b] $data.company.name" },
{ "p": "$data.company.address" },
{ "p": "$data.company.city" }
],
[
{ "p": "[b]To:[/b] $data.client.companyName" },
{ "p": "$data.client.address" },
{ "p": "$data.client.city" }
]
]
}
},
{
"table": {
"source": "$data.lineItems",
"map": ["service", "hours", "rate", "total"],
"widths": [4, 1, 1, 1],
"rows": [
[
{ "p": "[b]Service[/b]" },
{ "p": "[b]Hours[/b]" },
{ "p": "[b]Rate[/b]" },
{ "p": "[b]Total[/b]" }
]
]
}
},
{ "p": "Grand Total: $data.invoice.grandTotal", "style": "total" },
{ "p": "" },
{ "p": "Payment Terms:" },
{
"ol": {
"source": "$data.paymentTerms"
}
},
{ "p": "" },
{ "p": "Thank you for your business, $data.client.contactName." }
]
}
}
Data object
{
"document": {
"title": "Professional Services Invoice",
"date": "March 29, 2026"
},
"company": {
"name": "ShipForge Ltd",
"address": "45 Innovation Drive",
"city": "London, UK"
},
"client": {
"companyName": "Meridian Logistics",
"contactName": "Sarah Mitchell",
"address": "742 Evergreen Terrace",
"city": "Denver, CO"
},
"lineItems": [
{ "service": "Platform setup", "hours": "80", "rate": "$150", "total": "$12,000" },
{ "service": "API integration", "hours": "40", "rate": "$175", "total": "$7,000" },
{ "service": "User training", "hours": "16", "rate": "$125", "total": "$2,000" }
],
"invoice": {
"grandTotal": "$21,000"
},
"paymentTerms": [
"Wire transfer to ShipForge Ltd account",
"Reference: INV-SF-2026-0329",
"Net 30 days from invoice date"
]
}
Generated output
The same template, called with different data objects, produces personalized invoices for each client -- different names, addresses, line items, and totals -- all from a single stored template.
Composing templates from components
Templates can reference other templates as reusable components via ref. Instead of duplicating headers, footers, and common blocks across templates, extract them into components and compose:
{
"document": {
"pageSetup": { "size": "LETTER", "margins": [30, 30, 30, 30] },
"content": [
{ "ref": { "id": "component-header-guid", "name": "company-header" } },
{ "h1": "Invoice #$data.invoice.number" },
{ "ref": { "id": "component-table-guid", "name": "line-items" } },
{ "ref": { "id": "component-footer-guid", "name": "signature-block" } }
]
}
}
Components use "component" as their root key and are stored as regular templates. The composer resolves all refs, merges styles, and flattens content before data injection runs.
For full details on component structure, data scoping, merge rules, and nesting, see Composition.
Template revisions
Templates support versioning through revisions. When you update a template, the previous version is retained as a parent revision. See the Templates API for revision management endpoints.
Test data
Templates support an optional testData field that stores sample data alongside the template. This is used in the Playground for previewing the template without requiring a separate data payload.
{
"name": "Shipping Invoice",
"content": "{ ... document definition ... }",
"testData": "{ \"client\": { \"companyName\": \"Test Corp\" }, ... }",
"createdBy": "laura.bennett@shipforge.io"
}