Product and API guide

Template JSON schema

The shape of a definition

A template definition is one object with two parts: page, which decides the sheet, and elements, the ordered list that is drawn onto it.

JSON
{
  "page": {
    "size": "A4",
    "orientation": "portrait",
    "margin": { "top": 48, "right": 48, "bottom": 48, "left": 48 }
  },
  "elements": [
    {
      "id": "invoice-title",
      "type": "heading",
      "placementMode": "flow",
      "content": { "text": "Invoice" },
      "layout": { "width": 100, "widthUnit": "percent", "height": 40 },
      "style": { "fontSize": 24, "textStyle": "bold" }
    }
  ]
}

Page sizes: A4 · A3 · Letter. Orientation is portrait or landscape, and margins are in CSS pixels at 96 per inch.

Which element accepts which field

Every element carries id, type, placementMode. Everything else depends on the type, and the table below is generated from the same rule set the validator uses - it cannot fall behind the product.

type Fields it accepts
chart band · binding · chart · content · dataRules · layout · style
container band · container · dataRules · layout
dataset band · binding · dataRules · layout · style
footer band · binding · content · dataRules · layout · style
header band · binding · content · dataRules · layout · style
heading band · binding · content · dataRules · layout · style
image band · binding · content · dataRules · layout · style
pagebreak band · dataRules
pagenumber band · content · dataRules · layout · style
table band · binding · columns · content · dataRules · layout · style · summaryRows
text band · binding · content · dataRules · layout · style

A field that is not in the row for its type fails validation rather than being ignored. A silently dropped field is worse: the document comes out missing something and the request looked successful.

placementMode

How an element finds its position on the sheet. The mode also decides whether layout.x and layout.y are read at all.

  • flow — stacked one after another down the page; x and y are ignored and the element grows with its content
  • free — placed at layout.x and layout.y, exactly where you put it
  • band — repeated on every page in the band named by the element, such as a header or footer

The dataset element

A dataset prints a collection that is not a table: a list of visits, photographs or signatures, drawn with the layout the template gives it rather than in columns.

JSON
{
  "id": "maintenance-visits",
  "type": "dataset",
  "placementMode": "flow",
  "binding": "visits",
  "layout": { "width": 100, "widthUnit": "percent", "height": 160 },
  "style": { "fontSize": 11 },
  "dataRules": { "required": true }
}

binding names the array in your data, exactly as it does on a table. What the element draws for each item comes from the layout and style, so the same collection can be a table in one template and a dataset in another.

A dataset has no content field. It is fed only by binding - the text that used to sit in content never reached the output, so the field was removed rather than left as a place where writing has no effect.

Checking your data before you render

The validation endpoint answers the question a render would answer, without spending quota: does this data satisfy what the template requires?

cURL
curl -X POST https://api.pdfgine.com/api/v1/templates/invoice/validate-data \
  -H "X-PDFgine-Key: $PDFGINE_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data": { "referenceNo": "DOC-2026-0042", "currency": "USD" } }'
Data formats →

What usually goes wrong

  • A field that does not belong to its element type fails validation instead of being ignored. That is deliberate: a silently dropped field produces a document missing something, and nobody looks for what is not there.
  • In flow placement layout.x and layout.y are not read at all. Setting them and wondering why nothing moved is the most common schema question.
  • A dataset has no content field; it is fed only by binding. Text put in content never reaches the output.
  • Margins and sizes are CSS pixels at 96 per inch, not millimetres. A 20 written as millimetres comes out three times too small.
  • Rendering to find out whether the data fits costs a page. The validation endpoint answers the same question and costs nothing.