Template Expressions
Use {{ expression }} syntax in layer properties (content, src, color, etc.) to make templates dynamic. Expressions can reference brand tokens, variables, and call helper functions.
Fallbacks for optional brand assets
Brand tokens (logos, colors, images) may not always be defined. Use use or isDefined to provide fallbacks so templates work when assets are missing.
use (fallback chain)
Returns the first defined (non-null, non-undefined) value. Use for optional logos, colors, or images. Use function call syntax with parentheses and commas.
{{ use(brand.logos.logo, brand.logos.icon) }}
{{ use(brand.colors.primary, "#333333") }}
{{ use(brand.images.background, "https://picsum.photos/1200/630") }}
If brand.logos.logo is defined, it's used. Otherwise brand.logos.icon. If both are empty, returns "".
isDefined (alias)
Same as use — returns the first defined argument.
{{ isDefined(brand.logos.logo, default.logo) }}
Math functions
| Function | Example |
|---|---|
| min | {{ min(a, b) }} |
| max | {{ max(a, b) }} |
| abs | {{ abs(x) }} |
| round, ceil, floor | {{ round(3.7) }} |
| clamp | {{ clamp(value, 0, 100) }} — clamps value between min and max |
Calendar & date helpers
Month arguments are 1–12. Calculations use UTC. You cannot write array literals ([ ]) inside expressions; use the helpers below.
Loop source vs {{ }} in layer fields
| Where you use it | What happens |
|---|---|
| Loop source (repeater “Source” field) | The resolved value must be an array (or object). Put a variable path, a bare helper call (monthDayNumbers(year, month)), or one whole {{ expression }} there. Each item drives one iteration. |
{{ … }} in content, src, colors, etc. | The result is turned into a string for display. Arrays become comma-separated text (usually not what you want for a full list—use a repeater instead). Numbers and short strings render cleanly. |
Returns an array (for Loop source—or nested inside another helper)
Use these as the repeater source, or inside another call (e.g. range(1, daysInMonth(year, month)) uses the scalar daysInMonth inside an array helper).
| Function | Returns | Example Loop source | Notes |
|---|---|---|---|
range(count) | [0, …, count - 1] | range(7) | Seven columns (indices 0–6). |
range(start, end) | Inclusive integers | range(1, 31) | Often combined with daysInMonth (below). |
monthDayNumbers(year, month) | [1, …, lastDay] | monthDayNumbers(2026, 5) or monthDayNumbers(year, month) | One iteration per day in that month. |
yearRange(startYear, endYear) | Sorted inclusive years | yearRange(2020, 2030) | Year picker–style lists (length capped). |
calendarMonthSlots(year, month, weekStartsOn?) | 42 entries | calendarMonthSlots(2026, 5, 1) | null = empty cell; 1–31 = day of month. weekStartsOn: 0 Sun … 6 Sat; omit or use 1 for week starting Monday. |
monthNames() | 12 long names | monthNames() | Header row for months (iterate item + itemIndex). |
monthNamesShort() | 12 short names | monthNamesShort() | Compact month labels. |
weekdayNames() | 7 long names | weekdayNames() | Sun–Sat labels. |
weekdayNamesShort() | 7 short names | weekdayNamesShort() | Matches well with range(7) row layout if order aligns with weekStartsOn. |
Repeater source examples (bare form; wrapped {{ … }} with the same inner expression is equivalent):
range(7)
monthDayNumbers(2026, 5)
monthDayNumbers(year, month)
calendarMonthSlots(2026, 5, 1)
calendarMonthSlots(year, month, 1)
yearRange(2020, 2030)
weekdayNamesShort()
Inside repeated layers, use your item variable (e.g. day, slot) and dayIndex / slotIndex as in the editor binding reference.
Returns a single value (for {{ }} text/numbers—or inside array helpers)
| Function | Returns | Example in {{ }} |
|---|---|---|
daysInMonth(year, month) | Number of days (integer); 0 if invalid | {{ daysInMonth(2026, 5) }} → 31 |
daysInAMonth(year, month) | Same as daysInMonth | {{ daysInAMonth(year, month) }} |
Scalar inside an array helper (still one expression; no literal [ ]):
range(1, daysInMonth(year, month))
That produces an array from 1 through the last day—useful as a Loop source when monthDayNumbers(year, month) would do the same more directly.
What not to expect from {{ arrayHelper() }}
Expressions like {{ monthDayNumbers(2026, 5) }} or {{ weekdayNamesShort() }} evaluate to arrays but display as a single string (values joined with commas). For UI layouts, point the Loop source at the same helper (or a variable holding the array), not only {{ }} on one text layer.
Layer repeaters still accept variable paths (items, users.list) when they resolve to a non-null value—same resolution order as documented in the editor.
Number parsing (currency / formatted strings)
When prices come in as strings like "$1,234.50", "₦12,500", or "(1,200)" you can convert them to plain numbers with toNumber (alias parseNumber):
| Function | Returns | Example |
|---|---|---|
toNumber(value) | Parsed number, or 0 if not parseable | {{ toNumber('$1,234.50') }} → 1234.5 |
toNumber(value, fallback) | Parsed number, or fallback if not parseable | {{ toNumber(price, 0) }} |
parseNumber(value) | Alias for toNumber | {{ parseNumber('₦12,500') }} → 12500 |
What gets stripped before parsing:
- Currency symbols:
$ € £ ¥ ₦ ₹ ₽ ₩ ¢ ฿ ₪ ₫ ₱ ₴ ﷼and trailing/leading codes likekr,USD,EUR,GBP,JPY,NGN,INR,RUB,KRW,CNY,CAD,AUD,CHF - Thousands commas (en-US convention):
"1,234,567.89"→1234567.89 - Spaces, including narrow no-break (
\u202F) and thin (\u2009) - Trailing percent:
"50%"→50 - Accounting parentheses for negatives:
"(1,200)"→-1200
sumItems already applies this normalization internally, so {{ sumItems(details.items, 'price.new') }} works even when price.new is "$1,234.50". Use toNumber explicitly when you need a single value (e.g. multiplying by a tax rate).
{{ toNumber(invoice.subtotal) * 1.1 }} // works even if subtotal is "$199.99"
{{ toNumber(invoice.discount, 0) }} // safe default when blank
{{ round(toNumber(price.new) - toNumber(price.discount)) }}
Strings using , as the decimal mark (e.g. "1.234,56") are not auto-converted — they're ambiguous against the en-US thousands convention. Normalize on input or replace the comma yourself first.
Array reducers (sums, joins)
For invoice-style layouts and lists where you need a total or a single string built from a repeater's source, use sumItems and joinItems. Both take the same array you would use as a loop.source (so reach for them via the variable path, e.g. details.items).
| Function | Returns | Example |
|---|---|---|
sumItems(items) | Total of numeric items | {{ sumItems(amounts) }} → sum of a number[] |
sumItems(items, path) | Total of path read from each item | {{ sumItems(details.items, 'price.new') }} |
joinItems(items) | Items joined with ", " | {{ joinItems(tags) }} |
joinItems(items, path) | path from each item, joined with ", " | {{ joinItems(speakers, 'name') }} |
joinItems(items, path, sep) | Custom separator | {{ joinItems(speakers, 'name', ' • ') }} |
Aliases sumLoopItems and joinLoopItems exist for parity with the loop.source mental model — they take the exact same arguments.
Rules:
- Non-array inputs return
0(sum) or""(join). null,undefined, and empty-string values are skipped — so a missing field never produces", , "gaps.sumItemsaccepts currency-formatted strings out of the box ("$1,234.50","₦12,500","(1,200)"for negatives). See Number parsing for the exact rules; truly non-numeric values are skipped.pathis a dot path ("price.new","address.city"); omit it when the array holds primitives directly.
Common patterns:
Total: {{ sumItems(details.items, 'price.new') }}
Net: {{ sumItems(details.items, 'price.new') - sumItems(details.items, 'discount') }}
Tags: {{ joinItems(tags) }}
Hosts: {{ joinItems(speakers, 'name', ' • ') }}
You can wrap reducers in other safe functions:
{{ round(sumItems(details.items, 'price.new') * 1.1) }} // 10% tax, rounded
{{ clamp(sumItems(orders, 'qty'), 0, 999) }} // sum capped to 3 digits
Color functions (chroma-js)
All color functions accept hex, named colors, or brand token paths like brand.colors.primary.
Light / dark
| Function | Description | Example |
|---|---|---|
| lighten | Lighten a color | {{ lighten(brand.colors.primary, 1.5) }} |
| darken | Darken a color | {{ darken(brand.colors.primary, 2) }} |
| saturate | Increase saturation | {{ saturate(brand.colors.accent, 2) }} |
| desaturate | Decrease saturation | {{ desaturate(brand.colors.primary, 1) }} |
| getLuminance | 0 (black) to 1 (white) | {{ getLuminance(brand.colors.primary) }} |
| isLight | True if luminance > 0.5 | {{ isLight(brand.colors.primary) }} |
| isDark | True if luminance < 0.5 | {{ isDark(brand.colors.primary) }} |
Mixing and blending
| Function | Description | Example |
|---|---|---|
| mix | Mix two colors | {{ mix(brand.colors.primary, brand.colors.secondary, 0.3) }} |
| shade | Mix with black | {{ shade(brand.colors.primary, 0.5) }} |
| tint | Mix with white | {{ tint(brand.colors.primary, 0.5) }} |
| blend | Blend modes (multiply, darken, lighten, screen, overlay, burn, dodge) | {{ blend(c1, c2, "multiply") }} |
| average | Average of colors | {{ average([c1, c2, c3]) }} |
Scales and gradients
| Function | Description | Example |
|---|---|---|
| scaleColors | Color at position (0–1) or array of n colors | {{ scaleColors([brand.colors.primary, brand.colors.secondary], 0.5) }} |
| scaleColors | Get 5 colors from gradient | {{ scaleColors(["#ff0000","#0000ff"], 5) }} |
Other color helpers
| Function | Description | Example |
|---|---|---|
| alpha | Set opacity (0–1) | {{ alpha(brand.colors.primary, 0.5) }} |
| luminance | Set luminance (0–1) | {{ luminance(brand.colors.primary, 0.5) }} |
| contrast | WCAG contrast ratio (4.5+ for text) | {{ contrast(brand.colors.primary, brand.colors.secondary) }} |
| hex | Get hex from any color | {{ hex(brand.colors.primary) }} |
| colorCss | Get rgb/rgba string | {{ colorCss(brand.colors.primary) }} |
| colorValid | Check if valid color | {{ colorValid(brand.colors.primary) }} |
| temperature | Color from Kelvin (2000=candle, 6500=daylight) | {{ temperature(3500) }} |
Brand token paths
| Path | Use case |
|---|---|
| brand.logos.logo, brand.logos.icon | Image src for logos |
| brand.logos.logoDark, brand.logos.logoLight | Light/dark variants |
| brand.colors.primary, brand.colors.secondary | Colors |
| brand.colors.darkTitleText, brand.colors.lightTitleText | Text on dark/light backgrounds |
| brand.typography.heading, brand.typography.body | Font families |
| brand.images.background, brand.images.picture | Hero/background images |
| brand.copy.tagline, brand.copy.description | Text content |
Best practices
- Always use fallbacks for optional assets —
{{ use(brand.logos.logo, brand.logos.icon) }}so the template works when a logo isn't set. - Use isLight/isDark for text contrast — Pick light or dark text based on background:
{{ isDark(brand.colors.primary) ? "#ffffff" : "#000000" }}(if your expression syntax supports ternaries). - Use contrast() — Ensure text readability: aim for 4.5:1 or higher.
- scaleColors for gradients — Generate consistent color ramps from brand colors.