Product and API guide

Table Data Builder

Open the builder

Select a table on the canvas. In the Inspector’s Data tab, choose Open Data Builder. The fields, source and preview tabs separate the table structure from its data.

Columns and summary rows

In Fields, add columns and set each heading and data field. Adjust width, formatting and advanced appearance as needed. You can add total, subtotal or note rows under the table; the Totals section explains how they are calculated.

Totals

On a total or subtotal row, choose a Calculation: Sum, Average, Minimum, Maximum or Count rows. The result is written in the Value column and is calculated from every row of that column; unless the row has a format of its own, it uses the column’s format.

JSON
"summaryRows": [
  { "type": "total", "label": "Grand total",
    "aggregate": "sum", "targetColumnKey": "total" }
]

Only cells that arrive as numbers count: empty cells are skipped, and an amount sent as text (“1,250.00”) leaves the result empty. A subtotal also covers every row, not each page. If you choose Data field instead, the Value box reads from the root of your data, not from the table: {{ grandTotal }}, for example.

Source and sample JSON

In Source, choose the table’s data array and edit the sample JSON. A column’s field must match a property of the objects in that array. For example, to show quantity from items, set the column field to quantity. Resolve any invalid JSON warning before continuing.

Default table appearance

Item Quantity Total
Fault diagnosis21.700,00
Connectivity module12.850,00
On-site calibration1850,00

The measurements are the product defaults: header 40px, row 40px, cell padding 10px, border 1px. A template can override each of them.

A table element, end to end

A table binds to an array in your data and draws one row per item. The element below is the one shipped with the Invoice template; the call under it feeds the same table three rows.

JSON
{
  "id": "invoice-items",
  "type": "table",
  "placementMode": "flow",
  "binding": "lineItems",
  "layout": { "x": 0, "y": 330, "width": 100, "widthUnit": "percent", "height": 220 },
  "style": {
    "tableHeaderHeight": 36,
    "tableRowHeight": 34,
    "tableRepeatHeader": true,
    "tablePreventRowSplit": true
  },
  "columns": [
    { "key": "name", "label": "Description", "valueTemplate": "{{ name }}",
      "align": "left", "widthPercent": 46 },
    { "key": "quantity", "label": "Quantity", "valueTemplate": "{{ quantity }}",
      "align": "right", "widthPercent": 14 },
    { "key": "unitPrice", "label": "Unit price", "valueTemplate": "{{ unitPrice }}",
      "align": "right", "widthPercent": 20,
      "format": { "kind": "currency", "currency": "USD", "decimals": 2 } },
    { "key": "total", "label": "Amount", "valueTemplate": "{{ total }}",
      "align": "right", "widthPercent": 20,
      "format": { "kind": "currency", "currency": "USD", "decimals": 2 } }
  ]
}
cURL
curl -X POST https://api.pdfgine.com/api/v1/render \
  -H "X-PDFgine-Key: $PDFGINE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "templateCode": "invoice",
        "format": "pdf",
        "locale": "en",
        "data": {
          "document": { "title": "Invoice Summary" },
          "referenceNo": "DOC-2026-0042",
          "issueDate": "2026-09-06",
          "currency": "USD",
          "customer": "Northwind Trading Ltd.",
          "lineItems": [
            { "name": "Fault diagnosis", "quantity": 2, "unitPrice": 850.00 },
            { "name": "Connectivity module", "quantity": 1, "unitPrice": 2850.00 },
            { "name": "On-site calibration", "quantity": 1, "unitPrice": 850.00 }
          ]
        }
      }'

binding names the array in your data, key names the field inside each item, and valueTemplate is what gets printed. tableRepeatHeader carries the header onto every page and tablePreventRowSplit keeps a row from being cut in half at a page break.

Applying and testing long tables

Check column order, empty fields and summaries in Preview, then apply the changes to the table. Next test zero, one and many rows in the document preview. Long text, header height and row height affect pagination; do not rely only on a short sample.

Charts and reports →

Dataset elements

A dataset element renders the collection it is bound to as a table, and it derives its own columns from the data — you do not declare them. Use it when the shape of the collection is decided by the payload rather than by the template.

Bound value What the document shows
[{ "name": "…", "quantity": 2 }] One column per field, in the order the fields first appear. A row that omits a field still leaves that column in place.
[{ "party": { "name": "…" } }] Nested objects flatten into dotted columns such as party.name, up to three levels deep. An array inside a row stays in a single cell.
["urgent", "warranty"] One column whose cells are the values themselves; the heading is the last segment of the binding.
{ "status": "released" } A single row, one column per field of the object.
[] Nothing is drawn. An unbound dataset element draws nothing either.

Column headings are the field names. A dataset element carries no labels of its own — use a table element when you want to name the columns yourself.

What usually goes wrong

  • A binding that names an array your data does not have draws an empty table, not an error. If the table comes out blank, compare the binding with the key in your request.
  • A column key is a property of the objects in that array. One misspelt key empties one column while the rest of the table looks healthy.
  • Amounts and quantities have to arrive as numbers. A string skips the formatter, so the column loses its alignment and its thousands separator.
  • A long table crosses pages. Without tableRepeatHeader the second page starts with bare rows, and without tablePreventRowSplit a row can be cut in half.
  • Test zero, one and many rows. A table that only ever saw three rows hides both the empty case and the page break.