Template examples

Each example shows the input, the expected result, and the template to use. Comments in the results explain what the template does; they are not part of the output.

Symbol Meaning
✅ Value preserved
♻️ Input value reused
➕ Value added
✨ Value transformed
🧹 Field or list item omitted
🛟 Fallback to null
🔢 Value calculated
🔍 Value validated
🛑 Error stops processing

Send an order confirmation

Input JSONExpected Result
{
  "name": "Ada",
  "total": 19.95,
  "paid": false,
  "items": [
    "Notebook"
  ],
  "quantities": [
    2
  ]
}
{
  "customer": "Ada", // ♻️ Input name reused as customer
  "total": 19.95,
  "paid": false, // ✅ Boolean preserved
  "items": [
    "Notebook"
  ],
  "quantities": [
    2
  ],
  "currency": "USD" // ➕ Fixed currency added
}

Template to use:

{
  "customer": "{{name:string}}",
  "total": "{{total:number}}",
  "paid": "{{paid:boolean}}",
  "items": "{{items:string[]}}",
  "quantities": "{{quantities:number[]}}",
  "currency": "USD"
}

Restrict form fields to static values

Use in [...] for one value and [...] after the type for multiple values. A field is mandatory unless ?? omit makes it optional. For multiple values, || throw also rejects an empty array.

Static values are comma-separated and are not limited to strings. The rating below accepts only one to five stars. Numbers also work for multiple values (number[1, 2, 3]), and boolean static values work for one value (boolean in [true, false]).

Input JSONExpected Result
{
  "requiredColor": "black",
  "optionalColor": "green",
  "rating": 5,
  "requiredColors": [
    "black",
    "white"
  ]
}
{
  "requiredColor": "black", // 🔍 Mandatory single value accepted
  "optionalColor": "green", // 🔍 Optional single value accepted
  "rating": 5, // 🔍 Rating is between one and five stars
  "requiredColors": [ // 🔍 Mandatory non-empty list accepted
    "black",
    "white"
  ]
  // 🧹 optionalColors omitted when missing
}

Template to use:

{
  "requiredColor": "{{requiredColor:string in [black, white, green]}}",
  "optionalColor": "{{optionalColor:string in [black, white, green] ?? omit}}",
  "rating": "{{rating:number in [1, 2, 3, 4, 5]}}",
  "requiredColors": "{{requiredColors:string[black, white, green] || throw}}",
  "optionalColors": "{{optionalColors:string[black, white, green] ?? omit}}"
}

Describe a consumer-owned form

Add a final @ name suffix when a UI consumer should collect or present a variable in a particular way. The name is opaque to payload-vars: consumers define their own vocabulary. A lowercase category and optional style—such as choice, date.picker, or text.multiline—is a useful convention.

{
  "title": "Approval request",
  "sections": [
    {
      "title": "Request details",
      "content": {
        "Plan": "{{plan:string}}",
        "Description": "{{description:string ?? omit @ text.multiline}}",
        "Billing period": "{{period:string in [monthly, annual] @ choice}}",
        "Extensions": "{{extensions:string[reporting, audit log, single sign-on] || throw @ choice}}",
        "Needed from": "{{neededFrom:string @ date.picker}}",
        "Rating": "{{rating:number in [1, 2, 3, 4, 5] ?? omit @ rating.stars}}"
      }
    }
  ]
}

The sections structure and labels belong to the consumer. Payload-vars only exposes paths and contracts. Before any payload exists, variables() provides values such as:

{
  name: 'extensions',
  type: 'string[]',
  allowedValues: ['reporting', 'audit log', 'single sign-on'],
  valueFallback: { operator: '||', action: 'throw' },
  interaction: 'choice',
  paths: ['$.sections[0].content.Extensions']
}

A GitHub-renderable sketch of one possible consumer UI:

Plan

Business (read-only)

Description

[Add reporting and audit-log extensions…]

Billing period

◉ Monthly

○ Annual

Extensions

  • Reporting
  • Audit log
  • Single sign-on

Needed from

2026-11-01

Rating

★ ★ ★ ★ ☆

The consumer flow is: construct the template, call variables(), interpret interaction using the consumer’s own dictionary, collect ordinary payload data, and call render(payload). Interaction hints never change validation; static options still come from allowedValues, while a consumer may also source dynamic options itself.

Keep a place for a missing delivery note

?? null keeps empty text, zero, and false.

Input JSONExpected Result
{
  "nickname": "",
  "discount": 0,
  "paid": false,
  "note": null
}
{
  "nickname": "", // ✅ Empty text kept
  "discount": 0, // ✅ Zero kept
  "paid": false, // ✅ False kept
  "note": null,
  "instructions": null // 🛟 Missing value becomes null
}

Template to use:

{
  "nickname": "{{nickname:string ?? null}}",
  "discount": "{{discount:number ?? null}}",
  "paid": "{{paid:boolean ?? null}}",
  "note": "{{note:string ?? null}}",
  "instructions": "{{instructions:string ?? null}}"
}

Store unanswered form fields as null

Input JSONExpected Result
{
  "nickname": "",
  "age": 0,
  "subscribed": false
}
{
  "nickname": null, // 🛟 Empty text becomes null
  "age": null, // 🛟 Zero becomes null
  "subscribed": null // 🛟 False becomes null
}

Template to use:

{
  "nickname": "{{nickname:string || null}}",
  "age": "{{age:number || null}}",
  "subscribed": "{{subscribed:boolean || null}}"
}

Leave out an optional delivery note

Input JSONExpected Result
{
  "name": "Ada",
  "note": null
}
{ // 🧹 note and instructions omitted
  "name": "Ada"
}

Template to use:

{
  "name": "{{name:string}}",
  "note": "{{note:string ?? omit}}",
  "instructions": "{{instructions:string ?? omit}}"
}

Leave blank profile fields out of an update

Input JSONExpected Result
{
  "name": "Ada",
  "nickname": ""
}
{ // 🧹 Blank nickname omitted
  "name": "Ada"
}

Template to use:

{
  "name": "{{name:string}}",
  "nickname": "{{nickname:string || omit}}"
}

Require a stock count, even when it is zero

A missing count fails; a supplied 0 is allowed.

Input JSONExpected Result
{}
🛑 Throws FALLBACK_THROW

Template to use:

{
  "stock": "{{stock:number ?? throw}}"
}

Require acceptance of the terms

Input JSONExpected Result
{
  "accepted": false
}
🛑 Throws FALLBACK_THROW

Template to use:

{
  "accepted": "{{accepted:boolean || throw}}"
}

Keep missing daily readings in their original positions

Input JSONExpected Result
{
  "readings": [
    12,
    null,
    0
  ]
}
{
  "readings": [
    12,
    null, // ✅ Null kept in its original position
    0 // ✅ Zero kept
  ]
}

Template to use:

{
  "readings": "{{readings:number[ ?? null ]}}"
}

Mark blank survey answers as unanswered

Input JSONExpected Result
{
  "answers": [
    "Yes",
    "",
    null
  ]
}
{
  "answers": [
    "Yes",
    null, // 🛟 Blank answer becomes null
    null // ✅ Null kept in its original position
  ]
}

Template to use:

{
  "answers": "{{answers:string[ || null ]}}"
}

Remove missing readings but keep zero readings

Input JSONExpected Result
{
  "readings": [
    12,
    null,
    0
  ]
}
{
  "readings": [ // 🧹 Missing reading removed
    12,
    0 // ✅ Zero kept
  ]
}

Template to use:

{
  "readings": "{{readings:number[ ?? omit ]}}"
}

Remove blank product tags

Input JSONExpected Result
{
  "tags": [
    "new",
    "",
    null,
    "sale"
  ]
}
{
  "tags": [ // 🧹 Blank and null tags removed
    "new",
    "sale"
  ]
}

Template to use:

{
  "tags": "{{tags:string[ || omit ]}}"
}

Require a reading for every day

Input JSONExpected Result
{
  "readings": [
    12,
    null,
    0
  ]
}
🛑 Throws FALLBACK_THROW

Template to use:

{
  "readings": "{{readings:number[ ?? throw ]}}"
}

Reject a guest list with a blank name

Input JSONExpected Result
{
  "guests": [
    "Ada",
    ""
  ]
}
🛑 Throws FALLBACK_THROW

Template to use:

{
  "guests": "{{guests:string[ || throw ]}}"
}

Accept optional tag lists and clean their contents

Use || omit when an explicitly empty list should be omitted. Use ?? omit when an empty list should remain [] and only a missing or nullish list should be omitted.

Input JSONExpected Result
{
  "tags": [
    "new",
    "",
    null
  ],
  "savedTags": [],
  "suggestedTags": null
}
{ // 🧹 Missing extraTags omitted
  "tags": [ // 🧹 Blank and null tags removed
    "new"
  ],
  // savedTags omitted because [] triggers || omit
  "suggestedTags": null // ✅ Null list kept
}

Template to use:

{
  "tags": "{{tags:string[ || omit ] ?? omit}}",
  "savedTags": "{{savedTags:string[] || omit}}",
  "suggestedTags": "{{suggestedTags:string[] ?? null}}",
  "extraTags": "{{extraTags:string[] ?? omit}}"
}

Reuse the customer name for delivery

Input JSONExpected Result
{
  "name": "Ada"
}
{
  "customer": {
    "name": "Ada" // ♻️ Input name reused
  },
  "delivery": { // 🧹 Missing note omitted
    "recipient": "Ada" // ♻️ Name reused for delivery
  }
}

Template to use:

{
  "customer": {
    "name": "{{name:string}}"
  },
  "delivery": {
    "recipient": "{{name:string}}",
    "note": "{{note:string ?? omit}}"
  }
}

Build a packing slip without an optional note

Input JSONExpected Result
{
  "name": "Ada",
  "total": 19.95
}
[ // 🧹 Missing note removed from the list
  "Ada",
  19.95
]

Template to use:

[
  "{{name:string}}",
  "{{note:string ?? omit}}",
  "{{total:number}}"
]

Return just the cleaned customer name

Input JSONExpected Result
{
  "name": "  Ada  "
}
"Ada" // ✨ Surrounding whitespace trimmed

Template to use:

"{{name:string > text.trim}}"

Clean an email address and extract its domain

> changes the value; ! checks it. Read the steps from left to right.

Input JSONExpected Result
{
  "email": "  Ada@Example.com  "
}
{
  "domain": "example.com" // ✨ Domain extracted; 🔍 domain validated
}

Template to use:

{
  "domain": "{{email:string > text.trim ! email.email > email.domain ! email.domain}}"
}

Reject an invalid contact email

Input JSONExpected Result
{
  "email": "not-an-email"
}
🛑 Throws VALIDATION_FAILED

Template to use:

{
  "email": "{{email:string ! email.email}}"
}

Clean product tags before checking for duplicates

Input JSONExpected Result
{
  "tags": [
    " new ",
    "",
    null,
    "sale"
  ]
}
{
  "tags": [ // 🧹 Blank tags removed; 🔍 uniqueness checked
    "NEW", // ✨ Trimmed and uppercased
    "SALE" // ✨ Uppercased
  ]
}

Template to use:

{
  "tags": "{{tags:string[ > text.trim > style.upperCase || omit ] ! collection.unique}}"
}

Normalize delivery dates and keep free-text instructions

? applies the next > only when the check passes; other values stay unchanged.

Input JSONExpected Result
{
  "delivery": [
    "2026-10-01",
    "Call first"
  ]
}
{
  "delivery": [
    "2026-10-01T00:00:00.000Z", // ✨ Date normalized
    "Call first" // ✅ Non-date text unchanged
  ]
}

Template to use:

{
  "delivery": "{{delivery:string[ ? date.dateonly > date.isodatetime ]}}"
}

Normalize a delivery date, or trim the delivery instructions

~ handles values that fail the check.

Input JSONExpected Result
{
  "delivery": "  Call first  "
}
{
  "delivery": "Call first" // ✨ Alternate branch trims non-date text
}

Template to use:

{
  "delivery": "{{delivery:string ? date.dateonly > date.isodatetime ~ text.trim}}"
}

Trim a name only when it needs cleaning

Input JSONExpected Result
{
  "name": "  Ada  "
}
{
  "name": "Ada" // ✨ Trimmed after the check fails
}

Template to use:

{
  "name": "{{name:string ? text.trim ~ text.trim}}"
}

Calculate a shift length from the original timestamps

$.start and $.end read the input JSON. No hours input is needed.

Input JSONExpected Result
{
  "start": "2026-10-01T09:00:00Z",
  "end": "2026-10-01T17:00:00Z"
}
{
  "hours": 8 // 🔢 Calculated from input timestamps
}

Template to use:

{
  "hours": "{{hours:number = date.interval($.start,$.end) > date.msToHours}}"
}

Calculate a stay from dates already cleaned by the template

Bare paths such as stay.start read the template’s finished values.

Input JSONExpected Result
{
  "arrival": "2026-10-01",
  "departure": "2026-10-03"
}
{
  "stay": {
    "start": "2026-10-01T00:00:00.000Z", // ✨ Date normalized
    "end": "2026-10-03T00:00:00.000Z" // ✨ Date normalized
  },
  "hours": 48 // 🔢 Calculated from cleaned template dates
}

Template to use:

{
  "stay": {
    "start": "{{arrival:string > date.isodatetime}}",
    "end": "{{departure:string > date.isodatetime}}"
  },
  "hours": "{{hours:number = date.interval(stay.start,stay.end) > date.msToHours}}"
}

Use a cleaned start date with an original end timestamp

> core.omit hides the helper field while keeping its value available to functions.

Input JSONExpected Result
{
  "start": "2026-10-01",
  "end": "2026-10-01T06:00:00Z"
}
{ // 🧹 Helper start field omitted
  "hours": 6 // 🔢 Calculated from cleaned start and original end
}

Template to use:

{
  "start": "{{start:string > date.isodatetime > core.omit}}",
  "hours": "{{hours:number = date.interval(start,$.end) > date.msToHours}}"
}

Calculate time between two scheduled stops

Input JSONExpected Result
{
  "first": "2026-10-01T09:00:00Z",
  "last": "2026-10-01T12:00:00Z"
}
{
  "stops": [
    "2026-10-01T09:00:00Z",
    "2026-10-01T12:00:00Z"
  ],
  "hours": 3 // 🔢 Calculated from the two stops
}

Template to use:

{
  "stops": [
    "{{first:string}}",
    "{{last:string}}"
  ],
  "hours": "{{hours:number = date.interval(stops[0],stops[1]) > date.msToHours}}"
}

Leave a zero-length appointment duration unset

A function result can use a fallback too.

Input JSONExpected Result
{
  "start": "2026-10-01T09:00:00Z",
  "end": "2026-10-01T09:00:00Z"
}
{
  "hours": null // 🛟 Zero-hour result becomes null
}

Template to use:

{
  "hours": "{{hours:number = date.interval($.start,$.end) > date.msToHours || null}}"
}

Turn imported tags into a list and count them

Split comma-separated tags into a list, then count them. This example requires the custom functions catalog.splitTags and catalog.count to be available in your application.

Input JSONExpected Result
{
  "tags": "new, sale"
}
{
  "tags": [ // ✨ Split and trimmed by a custom function
    "new",
    "sale"
  ],
  "tagCount": 2 // 🔢 Counted from the resulting list
}

Template to use:

{
  "tags": "{{tags:string[] = catalog.splitTags($.tags)}}",
  "tagCount": "{{tagCount:number = catalog.count(tags)}}"
}

See Syntax for the full template rules.