Learn by editing
Runnable examples
Each example includes its model, icedoc and actual rendered output. Open any example in the playground to change it.
Show Icecream notation literally
Backslashes escape delimiter markers. Quoted expression strings retain their existing escape rules.
Model JSON
{}
Icedoc
Write \<{Name}> to insert a name.
\<{#} if(Enabled == true)
\{#}>Rendered output
Write <{Name}> to insert a name.
<{#} if(Enabled == true)
{#}>Check required model properties
Strict mode is enabled. Remove Name to see a located error; Note is explicitly null and can use default.
Model JSON
{
"Name": "Ada",
"Note": null
}
Icedoc
Hello <{Name}>. Note: <{Note | default('None')}>Rendered output
Hello Ada. Note: NoneCalculate an invoice
Compute line totals, add supplied amounts and subtract payments. Currency formatting follows decimal arithmetic.
Model JSON
{
"Items": [
{
"Name": "Inspection",
"Quantity": 2,
"UnitPrice": 75,
"Amount": 150
},
{
"Name": "Report",
"Quantity": 1,
"UnitPrice": 50,
"Amount": 50
}
],
"Paid": 80
}
Icedoc
<{#} forEach(Items:i)
<{#loop.index + 1}>. <{i.Name}>: <{i.Quantity * i.UnitPrice | currency('en-AU')}>
{#}>
Total: <{Items | sum('Amount') | currency('en-AU')}>
Balance: <{(Items | sum('Amount')) - Paid | currency('en-AU')}>
<{#} if((Items | sum('Amount')) - Paid > 0)
Payment outstanding
{#}>Rendered output
1. Inspection: $150.00
2. Report: $50.00
Total: $200.00
Balance: $120.00
Payment outstanding
Summarise measurements
sum, avg, min and max operate on numeric collections. avg is an ordinary, unweighted mean.
Model JSON
{
"Scores": [
60,
75,
90
],
"Completed": 3,
"Total": 4
}
Icedoc
Total: <{Scores | sum}>
Average: <{Scores | avg | number('0.0')}>
Range: <{Scores | min}>–<{Scores | max}>
Spread: <{(Scores | max) - (Scores | min)}>
Progress: <{Completed / Total * 100 | number('0.0')}>%Rendered output
Total: 225
Average: 75.0
Range: 60–90
Spread: 30
Progress: 75.0%Handle empty summaries
An empty sum is zero. Other empty aggregates return null and can use default. Invalid items still raise errors.
Model JSON
{
"Scores": []
}
Icedoc
Total: <{Scores | sum}>
Average: <{Scores | avg | default('N/A')}>
Lowest: <{Scores | min | default('N/A')}>
Highest: <{Scores | max | default('N/A')}>Rendered output
Total: 0
Average: N/A
Lowest: N/A
Highest: N/AFormat a collection
Join items, count them and select typed endpoints. Quoted arguments may contain commas and pipes.
Model JSON
{
"Tags": [
"ice",
"cream",
"ink"
]
}
Icedoc
<{Tags | count}> tags: <{Tags | join(', ')}>. First: <{Tags | first | upper}>. Last: <{Tags | last}>. <{'a,b' | replace(',', ' | ')}>Rendered output
3 tags: ice, cream, ink. First: ICE. Last: ink. a | bEncode and shorten text
HTML and URL encoding are explicit. Truncation includes the suffix in its length limit.
Model JSON
{
"Name": "A\u0026B",
"Summary": "A longer description"
}
Icedoc
<{Name | html}> / <{Name | urlEncode}> / <{Summary | truncate(8)}>Rendered output
A&B / A%26B / A longe…Check quantities and emptiness
Use numeric comparisons, filtered operands and empty/notEmpty predicates. Zero and false are not empty.
Model JSON
{
"Items": [
1,
2
],
"Total": 125
}
Icedoc
<{#} if(notEmpty(Items) && Items | count >= 2 && Total > 100)
Ready
<{#} elseIf(empty(Items))
No items
<{#} else
Needs review
{#}>Rendered output
Ready
Inspect each loop position
The index starts at zero. First and last are booleans; nested loops can inspect #loop.parent.
Model JSON
{
"Items": [
"Ada",
"Lin"
]
}
Icedoc
<{#} forEach(Items:i)
<{#loop.index}>: <{i}> (first=<{#loop.first}>, last=<{#loop.last}>)
{#}>Rendered output
0: Ada (first=True, last=False)
1: Lin (first=False, last=True)
Normalize names and plurals
Naming filters share acronym and separator handling; plural includes common irregular words.
Model JSON
{
"Name": "HTTPServer_name",
"Entity": "Person"
}
Icedoc
<{Name | camel}> / <{Name | pascal}> / <{Name | snake}> / <{Name | kebab}> / <{Entity | plural}>Rendered output
httpServerName / HttpServerName / http_server_name / http-server-name / PeopleHello projection
Insert model properties and chain filters. Missing properties normally produce empty text.
Model JSON
{
"Name": "Ada",
"Domain": "finance"
}
Icedoc
Hello <{Name}>. Welcome to <{Domain | upper}>.Rendered output
Hello Ada. Welcome to FINANCE.Repeat a block
A loop binds the item to i. It leaves #this and #parent unchanged. Nested loops restore shadowed item variables and expose #loop metadata.
Model JSON
{
"Name": "Quote",
"Items": [
{
"Name": "Inspection",
"Quantity": 1
},
{
"Name": "Repairs",
"Quantity": 2
}
]
}
Icedoc
<{#} forEach(Items:i)
<{#this.Name}>: <{i.Name}> x<{i.Quantity}>
{#}>Rendered output
Quote: Inspection x1
Quote: Repairs x2
Choose a branch
Compare values with == or !=; numeric ordering is also supported. Combine comparisons with &&. One end line closes the whole chain.
Model JSON
{
"Status": "Pending",
"Enabled": true
}
Icedoc
<{#} if(Status == 'Approved' && Enabled == true)
Approved
<{#} elseIf(Status == 'Pending')
Pending review
<{#} else
Other
{#}>Rendered output
Pending review
Capture reusable text
set stores the rendered body without emitting it. The capture retains its final newline and normalizes line endings to LF.
Model JSON
{
"Name": "Ada"
}
Icedoc
<{#} set(#greeting)
Hello <{Name}>.
{#}>
<{#greeting | trim}> Welcome back.Rendered output
Hello Ada. Welcome back.Embed multiline text in JSON
jsonString escapes string contents. Keep the surrounding JSON quotes in the document.
Model JSON
{}
Icedoc
<{#} set(#note)
Hello
"World"
{#}>
{"note":"<{#note | jsonString}>"}Rendered output
{"note":"Hello\n\"World\"\n"}Compose an AI prompt
gpt.prompt is an application convention. Icecream emits this JSON; the host chooses a model, calls a service and validates its response.
Model JSON
{
"Topic": "rainwater collection"
}
Icedoc
<{#} set(#system)
Explain clearly. Return a JSON object.
{#}>
<{#} set(#user)
Explain <{Topic}>.
{#}>
{
"type": "gpt.prompt",
"system": "<{#system | jsonString}>",
"user": "<{#user | jsonString}>"
}Rendered output
{
"type": "gpt.prompt",
"system": "Explain clearly. Return a JSON object.\n",
"user": "Explain rainwater collection.\n"
}Describe an action request
sys.call and CreateQuote are literal text, not built-in commands. The host supplies authorization, schema validation and execution.
Model JSON
{
"CustomerId": "customer-123",
"Scope": "Inspect roof\nProvide report"
}
Icedoc
{"type":"sys.call","name":"CreateQuote","args":{"customerId":"<{CustomerId | jsonString}>","scope":"<{Scope | jsonString}>"}}Rendered output
{"type":"sys.call","name":"CreateQuote","args":{"customerId":"customer-123","scope":"Inspect roof\nProvide report"}}Compose documents and scopes
QuoteLine receives each item as #this. Its #parent is the caller's model and #root remains the original model. The standalone include opens and closes on the same line; the following end line closes the loop.
Model JSON
{
"Company": "Example Co",
"Customer": "Ada",
"Items": [
{
"Name": "Inspection",
"Quantity": 1
}
]
}
Included document: QuoteLine
<{#root.Company}> / <{#parent.Customer}> / <{#this.Name}> x<{Quantity}>
Icedoc
<{#} forEach(Items:i)
<{#} include(QuoteLine, i) {#}>
{#}>Rendered output
Example Co / Ada / Inspection x1
Serialize structured data
jsonc serializes this JsonObject as compact JSON. Capability names and schemas are interpreted by the receiving application.
Model JSON
{
"type": "capability.spec",
"name": "quote.generate",
"inputs": {
"scope": "string"
},
"outputs": {
"quoteHtml": "string"
}
}
Icedoc
<{#root | jsonc}>Rendered output
{"type":"capability.spec","name":"quote.generate","inputs":{"scope":"string"},"outputs":{"quoteHtml":"string"}}Output panes show text, never execute HTML or commands. Directive lines disappear; body newlines remain. See the whitespace rules for exact behaviour.
