Data binding is the process that connects your JSON data to your Concerto model and TemplateMark template. Understanding how binding works is essential for creating effective templates.
The binding pipeline
Data flows through the template system in this sequence:
JSON Data - Your input data in JSON format
Concerto Model - Validates the structure and types
Validation - Ensures data matches the model
TemplateMark Template - References data properties
Template Engine - Merges data with template
Output Document - Generated result (HTML, PDF, etc.)
Basic binding
The simplest form of binding maps JSON properties directly to template variables.
Model
Data
Template
Result
The $class property in your JSON data must exactly match the namespace and concept name from your Concerto model.
Nested object binding
When your model has nested concepts, you use the {{#clause}} block to bind to them.
Model
namespace [email protected]
concept Address {
o String line1
o String city
o String state
o String country
}
@template
concept Customer {
o Address address
}
Data
Template
{{#clause address}}
#### Address
{{line1}},
{{city}}, {{state}},
{{country}}
{{/clause}}
Inside a {{#clause}} block, you can directly reference properties of the nested object without prefixing them with the parent name.
Array binding
Arrays can be bound using list blocks or join operations.
Simple string arrays
Model:
Data:
Template:
Ordered:
{{#olist middleNames}}
- {{this}}
{{/olist}}
Unordered:
{{#ulist middleNames}}
- {{this}}
{{/ulist}}
Arrays of objects
Model:
concept OrderLine {
o String sku
o Integer quantity
o Double price
}
concept Order {
o OrderLine[] orderLines
}
Data:
{
"orderLines" : [
{
"$class" : "[email protected] " ,
"sku" : "ABC-123" ,
"quantity" : 3 ,
"price" : 29.99
},
{
"$class" : "[email protected] " ,
"sku" : "DEF-456" ,
"quantity" : 5 ,
"price" : 19.99
}
]
}
Template:
{{#ulist orderLines}}
- {{quantity}}x _{{sku}}_ @ £{{price as "0,0.00"}}
{{/ulist}}
Each iteration of the list has access to the properties of the current OrderLine object.
Optional field binding
Optional fields require special handling to avoid errors when the field is missing.
Model
namespace [email protected]
concept Probation {
o Integer months
}
@template
concept EmploymentOffer {
o String candidateName
o Probation probation optional
}
Template with conditional
{{#if probation}}
{{#clause probation}}
This offer includes a probation period of **{{months}} months** .
{{/clause}}
{{/if}}
The {{#if}} block checks if probation exists before trying to access its properties.
Using optional block
{{#optional loyaltyStatus}}
You have loyalty status: {{level}}.
{{else}}
You do not have a loyalty status.
{{/optional}}
Always check for the existence of optional fields before accessing their nested properties to avoid runtime errors.
Different data types support different formatting options.
DATE: {{startDate as "DD MMMM YYYY"}}
Time: {{startDate as "HH:mm:ss"}}
Short: {{startDate as "D MMMM YYYY"}}
With thousands separator: {{doubleValue as "0,0"}}
With decimals: {{price as "0,0.00"}}
With currency: {{salary as "0,0.00 CCC"}}
Enum values
Enums bind as strings:
{{#join items locale="en" style="long"}}{{/join}}
For enum array ["CAR", "ACCESSORIES", "SPARE_PARTS"], this produces: “CAR, ACCESSORIES, and SPARE_PARTS”
Complex binding example
Let’s look at a complete employment offer that demonstrates multiple binding techniques.
Model
namespace [email protected]
concept MonetaryAmount {
o Double doubleValue
o String currencyCode
}
concept Probation {
o Integer months
}
@template
concept EmploymentOffer {
o String candidateName
o String companyName
o String roleTitle
o MonetaryAmount annualSalary
o DateTime startDate
o Probation probation optional
}
Data
{
"$class" : "[email protected] " ,
"candidateName" : "Ishan Gupta" ,
"companyName" : "Tech Innovators Inc." ,
"roleTitle" : "Junior AI Engineer" ,
"annualSalary" : {
"$class" : "[email protected] " ,
"doubleValue" : 85000 ,
"currencyCode" : "USD"
},
"startDate" : "2025-02-01T09:00:00.000Z" ,
"probation" : {
"$class" : "[email protected] " ,
"months" : 3
}
}
Template
DATE: {{startDate as "DD MMMM YYYY"}}
Dear {{candidateName}},
We are pleased to offer you the position of **{{roleTitle}}** at **{{companyName}}** .
{{#clause annualSalary}}
Your annual gross salary will be **{{doubleValue as "0,0"}} {{currencyCode}}** .
{{/clause}}
{{#if probation}}
{{#clause probation}}
This offer includes a probation period of **{{months}} months** .
{{/clause}}
{{/if}}
Sincerely,
**Human Resources**
{{companyName}}
This template demonstrates:
Simple property binding: {{candidateName}}, {{roleTitle}}
Date formatting: {{startDate as "DD MMMM YYYY"}}
Nested object binding: {{#clause annualSalary}}
Number formatting: {{doubleValue as "0,0"}}
Optional field handling: {{#if probation}}
You can access data properties in TypeScript formulas using standard JavaScript:
Your name has {{% return name.length %}} characters.
Order placed {{% return now.diff(order.createdAt, 'day')%}} days ago.
Total: {{% return order.orderLines.map(ol => ol.price * ol.quantity).reduce((sum, cur) => sum + cur).toFixed(2);%}}
Formulas have access to:
All properties in the current scope
The now object for date operations (using moment.js)
Standard JavaScript methods and operations
Validation and error handling
The binding process includes strict validation. From store.ts:79-108, the rebuild function:
async function rebuild ( template : string , model : string , dataString : string ) : Promise < string > {
const modelManager = new ModelManager ({ strict: true });
modelManager . addCTOModel ( model , undefined , true );
await modelManager . updateExternalModels ();
const engine = new TemplateMarkInterpreter ( modelManager , {});
const templateMarkTransformer = new TemplateMarkTransformer ();
const templateMarkDom = templateMarkTransformer . fromMarkdownTemplate (
{ content: template },
modelManager ,
"contract" ,
{ verbose: false }
);
const data = JSON . parse ( dataString );
const ciceroMark = await engine . generate ( templateMarkDom , data );
// ...
}
Errors occur when:
Data doesn’t match the model structure
Required fields are missing
Field types don’t match (string vs number, etc.)
$class property is incorrect or missing
Referenced properties don’t exist in the model
The playground displays binding errors in the Problems panel, helping you identify and fix issues quickly.
Scope rules
Understanding scope is crucial for correct binding:
Root scope
At the root level, you have access to all properties of the @template concept:
{{candidateName}} - Available at root
{{companyName}} - Available at root
Clause scope
Inside a {{#clause}} block, scope changes to that object:
{{#clause annualSalary}}
{{doubleValue}} - Available inside clause
{{currencyCode}} - Available inside clause
{{candidateName}} - NOT available (wrong scope)
{{/clause}}
List scope
Inside list blocks, scope is the current item:
{{#ulist orderLines}}
{{sku}} - Current item's sku
{{quantity}} - Current item's quantity
{{this}} - Reference to entire current item
{{/ulist}}
Common binding patterns
{{#if probation}}
Content only shown when probation exists.
{{/if}}
{{#clause address}}
{{line1}}, {{city}}
{{/clause}}
{{#ulist items}}
- {{this}}
{{/ulist}}
Debugging binding issues
When data binding fails:
Check the $class property - It must exactly match your namespace and concept name
Verify required fields - Ensure all non-optional fields are present in your data
Match property names - Property names are case-sensitive
Validate nested objects - Each nested object needs its own $class property
Check scope - Ensure you’re referencing properties in the correct scope
Review the Problems panel - The playground shows detailed error messages
Next steps
Template engine Learn how the engine processes bindings
TemplateMark Review TemplateMark syntax for binding