Appearance
Data Validation
When you build a template that other people or systems send data to, there's no guarantee the data will have the right shape. Press adapts as much as possible to the input it receives to render clean PDFs — a missing field may leave a blank space, but whether to allow that is the designer's choice. The <schema> element gives designers that choice, letting them limit the input scope of their templates — defining exactly what data is accepted and rejecting anything that doesn't match, with clear, custom error messages.
Defining a Schema
Add a <schema type="json"> element as a direct child of <press>. Inside it, write a standard JSON Schema definition:
xml
<press>
<schema type="json">
{
"type": "object",
"properties": {
"name": { "type": "string" },
"total": { "type": "number" }
},
"required": ["name", "total"]
}
</schema>
<data type="json">
{
"name": "Meridian Consulting",
"total": 1800
}
</data>
<document>
<page>
<frame padding="1cm">
<h1>{{ data.name }}</h1>
<p>Total: {{ data.total }}</p>
</frame>
</page>
</document>
</press>When the data matches the schema, the document renders normally. When it doesn't, you get an error pointing to the <data> element with a description of what failed.
Custom Error Messages
By default, validation errors use technical JSON Schema messages like must be string or must have required property 'name'. You can replace these with your own messages using the errorMessage keyword on any property:
xml
<schema type="json">
{
"type": "object",
"properties": {
"name": {
"type": "string",
"errorMessage": "Name must be a text value"
},
"items": {
"type": "array",
"minItems": 1,
"errorMessage": "At least one item is required"
}
},
"required": ["name", "items"]
}
</schema>If someone sends {"name": 123, "items": []}, the error will read:
The data provided does not match the template's schema: Name must be a text value; At least one item is required
This makes it much easier for API consumers to understand what they need to fix.
Validating Nested Objects
Use standard JSON Schema nesting to validate complex data shapes:
xml
<schema type="json">
{
"type": "object",
"properties": {
"client": {
"type": "object",
"properties": {
"name": { "type": "string", "errorMessage": "Client name must be text" },
"email": { "type": "string", "errorMessage": "Client email must be text" }
},
"required": ["name"],
"errorMessage": "Client must be an object with at least a name"
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": { "type": "string" },
"amount": { "type": "number" }
},
"required": ["description", "amount"]
},
"minItems": 1,
"errorMessage": "At least one line item is required"
}
},
"required": ["client", "items"]
}
</schema>Rules
- Only one
<schema>element is allowed per document - The
typeattribute must be"json"— only JSON data is validated - The schema content must be valid JSON and a valid JSON Schema
- If no
<schema>is defined, no validation is performed - If a schema is present but there are no JSON
<data>elements, validation is skipped - CSV and XML data elements are not validated against the schema
Example: Invoice Template with Validation
Here's a template that limits its input scope to only the fields it expects, giving API consumers clear feedback when their data doesn't match:
xml
<press>
<schema type="json">
{
"type": "object",
"properties": {
"invoice_number": {
"type": "string",
"errorMessage": "Invoice number is required and must be text"
},
"date": {
"type": "string",
"errorMessage": "Date is required and must be text"
},
"to": {
"type": "object",
"properties": {
"name": { "type": "string" },
"company": { "type": "string" }
},
"required": ["name"],
"errorMessage": "Recipient must include at least a name"
},
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"description": { "type": "string" },
"total": { "type": "number" }
},
"required": ["description", "total"]
},
"errorMessage": "At least one line item is required"
},
"grand_total": {
"type": "number",
"errorMessage": "Grand total must be a number"
}
},
"required": ["invoice_number", "date", "to", "items", "grand_total"]
}
</schema>
<document format="A4" page-margin="2cm">
<page>
<frame direction="row">
<frame width="50%">
<frame font-size="10pt">Invoice To:</frame>
<frame font-size="14pt" font-style="bold">{{ data.to.name }}</frame>
<frame font-size="10pt" show-if="data.to.company">{{ data.to.company }}</frame>
</frame>
<frame width="50%" text-align="right">
<frame font-size="24pt" font-style="bold">INVOICE</frame>
<frame font-size="10pt">No: {{ data.invoice_number }}</frame>
<frame font-size="10pt">Date: {{ data.date }}</frame>
</frame>
</frame>
<table>
<tr>
<th width="70%">Description</th>
<th text-align="right">Total</th>
</tr>
<repeat data="data.items" item="line">
<tr>
<td>{{ line.description }}</td>
<td text-align="right">£{{ line.total }}</td>
</tr>
</repeat>
</table>
<frame h-align="right" padding-top="12pt">
<frame font-style="bold" font-size="14pt">
Grand Total: £{{ data.grand_total }}
</frame>
</frame>
</page>
</document>
<data type="json">
{
"invoice_number": "#12345",
"date": "15 January 2026",
"to": { "name": "Helena Torres", "company": "Apex Industries" },
"items": [
{ "description": "Strategy workshop (8 hrs)", "total": 1200 },
{ "description": "Implementation review (4 hrs)", "total": 600 }
],
"grand_total": 1800
}
</data>
</press>With this schema in place, sending data without items or with a string where grand_total should be a number will return a clear, actionable error telling the API consumer exactly what to fix.