Skip to main content
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:
  1. JSON Data - Your input data in JSON format
  2. Concerto Model - Validates the structure and types
  3. Validation - Ensures data matches the model
  4. TemplateMark Template - References data properties
  5. Template Engine - Merges data with template
  6. Output Document - Generated result (HTML, PDF, etc.)

Basic binding

The simplest form of binding maps JSON properties directly to template variables.

Model

namespace [email protected]

@template
concept HelloWorld {
    o String name
}

Data

{
    "$class": "[email protected]",
    "name": "John Doe"
}

Template

### Hello {{name}}!

Result

Hello John Doe!
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

{
    "$class": "[email protected]",
    "address": {
        "$class": "[email protected]",
        "line1": "1 Main Street",
        "city": "Boston",
        "state": "MA",
        "country": "USA"
    }
}

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:
namespace [email protected]

@template
concept Person {
    o String[] middleNames
}
Data:
{
    "$class": "[email protected]",
    "middleNames": ["Tenzin", "Isaac", "Mia"]
}
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.

Type-based formatting

Different data types support different formatting options.

DateTime formatting

DATE: {{startDate as "DD MMMM YYYY"}}
Time: {{startDate as "HH:mm:ss"}}
Short: {{startDate as "D MMMM YYYY"}}

Number formatting

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}}

Binding in formulas

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}}
{{date as "DD MMMM YYYY"}}
{{amount as "0,0.00"}}

Debugging binding issues

When data binding fails:
  1. Check the $class property - It must exactly match your namespace and concept name
  2. Verify required fields - Ensure all non-optional fields are present in your data
  3. Match property names - Property names are case-sensitive
  4. Validate nested objects - Each nested object needs its own $class property
  5. Check scope - Ensure you’re referencing properties in the correct scope
  6. 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

Build docs developers (and LLMs) love