Data Source
The source property populates tables and lists from arrays in your data object. This is how you generate dynamic-length content -- tables with a variable number of rows, or lists with a variable number of items.
Table data source
Properties
| Property | Type | Description |
|---|---|---|
source | string | $data path pointing to an array of objects |
map | string[] | Array of object keys, mapped to columns left-to-right |
How it works
- Define the table with its
widthsand a header row inrows. - Set
sourceto a$datapath pointing to an array of objects. - Set
mapto specify which object keys map to which columns. - At generation time, one row is appended for each item in the array, with values pulled from the keys listed in
map.
The header row you define in rows is preserved. Data rows are appended after it.
Template
{
"table": {
"source": "$data.items",
"map": ["description", "qty", "amount"],
"widths": [3, 1, 1],
"rows": [
[
{ "p": "[b]Description[/b]" },
{ "p": "[b]Qty[/b]" },
{ "p": "[b]Amount[/b]" }
]
]
}
}
Data
{
"items": [
{ "description": "Cloud migration", "qty": "120", "amount": "$18,000" },
{ "description": "Security audit", "qty": "40", "amount": "$6,000" },
{ "description": "Training sessions", "qty": "16", "amount": "$2,400" }
]
}
Result
The table renders with the header row plus three data rows:
| Description | Qty | Amount |
|---|---|---|
| Cloud migration | 120 | $18,000 |
| Security audit | 40 | $6,000 |
| Training sessions | 16 | $2,400 |
Column mapping
The map array maps object keys to columns positionally:
map[0]maps to the first columnmap[1]maps to the second columnmap[2]maps to the third column
The number of entries in map should match the number of columns defined in widths.
List data source
A data-driven list uses source (and optionally map). For an array of strings, each string becomes a list item directly. For an array of objects, supply map with a $item.* template.
Properties
| Property | Type | Description |
|---|---|---|
source | string | $data path pointing to an array |
map | string | Optional template for each item (e.g. "$item.name — $item.role") |
How it works
- Define the list (
ulorol) withsourceinside it. - Set
sourceto a$datapath pointing to an array. - At generation time, one list item is created for each entry in the array.
When source is supplied, omit li — the items are produced from the data.
Unordered list
{
"ul": {
"source": "$data.notes"
}
}
Ordered list
{
"ol": {
"source": "$data.paymentTerms"
}
}
Data
{
"notes": [
"Payment due within 30 days",
"Late fees apply after due date",
"Contact billing@acme.com for questions"
],
"paymentTerms": [
"Wire transfer to ShipForge Ltd account",
"Reference: INV-SF-2026-0329",
"Net 30 days from invoice date"
]
}
Combined example: table and list
This example uses both a table source for line items and a list source for payment terms in a single document.
Template
{
"document": {
"styles": {
"title": { "fontSize": 20, "bold": true },
"subtitle": { "fontSize": 12, "italic": true, "color": "#555555" },
"total": { "bold": true, "fontSize": 14 }
},
"content": [
{ "p": "$data.document.title", "style": "title" },
{ "p": "Prepared for $data.client.companyName on $data.document.date", "style": "subtitle" },
{
"separator": {
"lineType": "SOLID",
"length": 500,
"width": 1,
"color": "#000000"
}
},
{
"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": "Payment Terms:" },
{
"ol": {
"source": "$data.paymentTerms"
}
},
{ "p": "Thank you for your business, $data.client.contactName." }
]
}
}
Data
{
"document": {
"title": "Professional Services Invoice",
"date": "March 29, 2026"
},
"client": {
"companyName": "Meridian Logistics",
"contactName": "Sarah Mitchell"
},
"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"
]
}
Result
The generated PDF contains:
- A heading "Professional Services Invoice" with the subtitle "Prepared for Meridian Logistics on March 29, 2026"
- A table with a header row and three data rows for the line items
- The grand total "Grand Total: $21,000"
- A numbered list of payment terms
- A closing line "Thank you for your business, Sarah Mitchell."
Key points
sourceandmapare placed as sibling properties alongside thetableorul/olobject, not inside it.- The header row in a table is preserved; data rows are appended after it.
- For lists, the
liarray should be empty when usingsource-- items are generated from the data array. sourcevalues are$datapaths, just like scalar references.- You can combine scalar
$datareferences andsourcein the same document.