Templates
Templates compute values when you write documents. Use them to combine fields, do math, format dates, generate IDs, and clean up text, all inside the request.
Overview
Templates are strings that contain {{ ... }} expressions. Singlebase renders them against the document being written, then saves the result.
Where templates work:
dataindata.insertanddata.updateinsert_dataandupdate_dataindata.upsert
Quick grammar:
| Syntax | Meaning |
|---|---|
{{ ... }} | Expression. The result is a string |
&:{{ ... }} | Native expression. Keeps the type: number, boolean, array, object, or null |
{{ $function() }} | Helper function call |
{{ $func1($func2(value)) }} | Nested helper calls |
{{ value | len }} | Filter |
{{ $now() | date_shift('2days') }} | Helper call followed by a filter |
Example:
json
{
"operation": "data.insert",
"payload": {
"collection": "orders",
"data": {
"customer": "ada lovelace",
"price": 12,
"quantity": 3,
"label": "{{ quantity }} x Notebook",
"total": "&:{{ price * quantity }}",
"customer_name": "{{ $titlecase(customer) }}",
"order_ref": "{{ $uppercase($shortid()) }}",
"due_on": "{{ $now() | date_shift('14days') | date_format('YYYY-MM-DD') }}"
}
}
}Saved document:
json
{
"customer": "ada lovelace",
"price": 12,
"quantity": 3,
"label": "3 x Notebook",
"total": 36,
"customer_name": "Ada Lovelace",
"order_ref": "9F2C41AB",
"due_on": "2026-10-17"
}Expression Syntax
{{ }} - String Expression
Evaluates the expression and returns a string. You can mix text and several expressions in one value.
json
{
"greeting": "Hello {{ first_name }}!",
"summary": "{{ title }} ({{ views }} views)"
}Characteristics:
- Always returns a string
- Supports interpolation with surrounding text
- A missing field renders as an empty string
&:{{ }} - Native Expression
Evaluates the expression and returns its native type.
json
{
"total": "&:{{ subtotal + tax }}",
"is_large": "&:{{ total >= 100 }}",
"tag_count": "&:{{ tags | len }}"
}Result:
json
{
"total": 125.5,
"is_large": true,
"tag_count": 3
}Characteristics:
- Returns a number, boolean, string, array, object, or null
- The whole value must be one expression: no surrounding text
- Keeps the key even when the result is null or empty
$function() - Helper Calls
Call a helper function with a $ prefix inside any expression.
json
{
"slug": "{{ $slugify(title) }}",
"id_hash": "{{ $sha256(email) }}",
"created": "{{ $now() }}"
}Helpers can be nested:
json
{
"code": "{{ $uppercase($substring($trim(name), 0, 3)) }}"
}| filter - Filters
Pipe a value through one or more filters. Filters can follow a field or a helper call.
json
{
"name_upper": "{{ name | trim | upper }}",
"tag_count": "&:{{ tags | len }}",
"expires_at": "{{ $now() | date_shift('30days') }}",
"tag_list": "{{ tags | join(', ') }}"
}Expressions
Inside {{ ... }} you can use:
| Kind | Example | Result |
|---|---|---|
| Field | {{ title }} | Value of title |
| Nested field | {{ profile.city }} | Value of profile.city |
| Array index | {{ items[0].name }} | First item's name |
| Math | &:{{ price * quantity }} | 36 |
| Comparison | &:{{ total >= 100 }} | true |
| Logic | &:{{ in_stock and price < 50 }} | true |
| String concat | {{ first_name ~ ' ' ~ last_name }} | "Ada Lovelace" |
| Literal | &:{{ [1, 2, 3] }} | [1, 2, 3] |
Math operators are +, -, *, /, and //, and they only accept numbers. + also joins strings. %, **, and division by zero are not allowed. Logic uses and, or, and not.
Templates run in a sandbox. Only {{ ... }} expressions are supported; {% ... %} blocks are not.
Evaluation order:
- Plain values and data operators are applied.
- Templates are rendered against the result.
So templates see the new values:
javascript
// Existing: { "views": 3 }
{
"views:$incr": 1,
"views_label": "{{ views }} views"
}
// Result: { "views": 4, "views_label": "4 views" }Helper Functions
Call helpers as $name(...). Arguments can be fields, literals, or other helpers.
Date and Time
| Helper | Description | Example | Result |
|---|---|---|---|
$now() | Current UTC date and time, ISO 8601 | {{ $now() }} | 2026-10-03T12:00:00+00:00 |
$date(value) | Normalize a date to ISO 8601. Defaults to now | {{ $date(published) }} | 2026-01-15T10:00:00+00:00 |
$date_format(date, format) | Format a date. Alias $formatdate | {{ $date_format(created, 'YYYY-MM-DD') }} | 2026-01-15 |
$date_shift(date, shift) | Shift a date. Alias $shiftdate | {{ $date_shift($now(), '+1 week') }} | One week from now |
Formats: ISO, ISO_DATE, ISO_TIME, ISO_DATETIME, or tokens such as YYYY-MM-DD, MMM D, YYYY, and HH:mm.
Shifts: Combine signed amounts and units: 2days, +3 hours, -1 week, 1 day - 3 hours, +2.5hours -30min. Units are y/year, mo/month, w/week, d/day, h/hour, min/minute, and s/second, singular or plural.
IDs and Hashing
| Helper | Description | Example result |
|---|---|---|
$uuid() | UUID v4 without dashes | 7918dbc69cdf440386816bbb4f0ec817 |
$uuid4() | UUID v4 with dashes | 49059b8c-3932-4784-bbb9-2e02ed05ae01 |
$uuid7() | Time-ordered UUID v7 | 01a100cb-f63d-7c03-855a-50e15703d3c5 |
$shortid() | 8-character random ID | 32fae10d |
$sha256(value) | SHA-256 hex digest | b5fc85e5...c41d72 |
Strings
| Helper | Description | Example | Result |
|---|---|---|---|
$uppercase(text) | Uppercase. Alias $upper | {{ $uppercase('api') }} | API |
$lowercase(text) | Lowercase. Alias $lower | {{ $lowercase('API') }} | api |
$titlecase(text) | Capitalize each word. Alias $title | {{ $titlecase('ada lovelace') }} | Ada Lovelace |
$capitalize(text) | Capitalize the first letter | {{ $capitalize('ada lovelace') }} | Ada lovelace |
$trim(text) | Remove surrounding whitespace | {{ $trim(' hi ') }} | hi |
$slugify(text) | URL-friendly slug | {{ $slugify('Hello World') }} | hello-world |
$substring(text, start, end) | Slice a string | {{ $substring('lovelace', 0, 4) }} | love |
$replace(text, old, new) | Replace text | {{ $replace('v1', 'v', 'version ') }} | version 1 |
$concat(a, b, ...) | Join values as text | {{ $concat(first, ' ', last) }} | Ada Lovelace |
$truncate(text, words, suffix) | Keep the first N words | {{ $truncate(bio, 3, '...') }} | Hello world this... |
$strip_html(html) | Remove HTML tags | {{ $strip_html('<b>Hi</b>') }} | Hi |
$maskstr(text, visible, char) | Mask all but the last N characters. Defaults 4, * | {{ $maskstr(card, 4) }} | ************1234 |
$count_words(text) | Number of words | &:{{ $count_words(body) }} | 120 |
$contains(text, part) | Text contains a substring | &:{{ $contains(title, 'API') }} | true |
$includes(value, item) | String, array, or object contains an item | &:{{ $includes(tags, 'api') }} | true |
$starts_with(text, prefix) | Starts with a prefix | &:{{ $starts_with(sku, 'A-') }} | true |
$ends_with(text, suffix) | Ends with a suffix | &:{{ $ends_with(email, '.edu') }} | false |
Numbers
| Helper | Description | Example | Result |
|---|---|---|---|
$incr(value, by) | Add by (default 1). Missing values start at 0 | &:{{ $incr(score, 5) }} | 15 |
$decr(value, by) | Subtract by (default 1) | &:{{ $decr(stock) }} | 9 |
$to_int(value, default) | Convert to an integer | &:{{ $to_int('42') }} | 42 |
$to_float(value, default) | Convert to a float | &:{{ $to_float('4.2') }} | 4.2 |
$round(x) | Round to the nearest integer | &:{{ $round(2.6) }} | 3 |
$floor(x) | Round down | &:{{ $floor(2.6) }} | 2 |
$ceil(x) | Round up | &:{{ $ceil(2.1) }} | 3 |
$abs(x) | Absolute value | &:{{ $abs(-3) }} | 3.0 |
$random(min, max) | Random integer. Defaults 0, 100 | &:{{ $random(1, 6) }} | 4 |
$to_currency(amount, currency) | Currency text. Default USD | {{ $to_currency(1234.5) }} | USD 1,234.50 |
$to_percent(value, decimals) | Percentage text. Default 1 decimal | {{ $to_percent(0.256) }} | 25.6% |
$to_ordinal(value) | Ordinal text | {{ $to_ordinal(3) }} | 3rd |
Arrays and Objects
| Helper | Description | Example | Result |
|---|---|---|---|
$len(value) | Length of a string, array, or object. Alias $length | &:{{ $len(tags) }} | 3 |
$sum(array) | Sum of numbers | &:{{ $sum(scores) }} | 6.0 |
$avg(array) | Average of numbers | &:{{ $avg(scores) }} | 2.0 |
$min(array) | Smallest number | &:{{ $min(scores) }} | 1.0 |
$max(array) | Largest number | &:{{ $max(scores) }} | 3.0 |
$sortby(array, key, reverse) | Sort objects by a key | &:{{ $sortby(items, 'qty', true) }} | Highest qty first |
$groupby(array, key) | Group objects by a key | &:{{ $groupby(items, 'type') }} | {"a": [...], "b": [...]} |
Conditions and Validation
| Helper | Description | Example | Result |
|---|---|---|---|
$cond(condition, yes, no) | If/else. Alias $condition | {{ $cond(total > 100, 'large', 'small') }} | large |
$default(value, fallback) | fallback when value is empty. 0 is kept | {{ $default(nickname, 'Guest') }} | Guest |
$is_empty(value) | Value is empty | &:{{ $is_empty(tags) }} | false |
$is_null(value) | Value is null or empty | &:{{ $is_null(deleted_at) }} | true |
$is_truthy(value) | Value is truthy | &:{{ $is_truthy(flag) }} | true |
$is_falsey(value) | Value is falsy | &:{{ $is_falsey(flag) }} | false |
$is_not(value) | Logical NOT | &:{{ $is_not(archived) }} | true |
$is_email(value) | Valid email address | &:{{ $is_email(email) }} | true |
$is_url(value) | Valid URL | &:{{ $is_url(website) }} | true |
$is_ip(value) | Valid IP address | &:{{ $is_ip(ip) }} | true |
Encoding
| Helper | Description | Example | Result |
|---|---|---|---|
$b64_encode(text) | Base64-encode text | {{ $b64_encode('hi') }} | aGk= |
$url_encode(text) | URL-encode text | {{ $url_encode('a b&c') }} | a%20b%26c |
Filters
Apply filters with |. Chain them left to right. Filters that take arguments use parentheses.
json
{
"title": "{{ raw_title | trim | title }}",
"publish_on": "{{ $now() | date_shift('+1 day') | date_format('YYYY-MM-DD') }}"
}| Filter | Description | Example |
|---|---|---|
trim | Remove surrounding whitespace | {{ name | trim }} |
upper, uppercase | Uppercase | {{ code | upper }} |
lower, lowercase | Lowercase | {{ email | lower }} |
title, titlecase | Capitalize each word | {{ name | title }} |
capitalize | Capitalize the first letter | {{ name | capitalize }} |
slugify | URL-friendly slug | {{ title | slugify }} |
strip_html | Remove HTML tags | {{ body | strip_html }} |
replace(old, new) | Replace text | {{ sku | replace('-', '') }} |
truncate(length) | Shorten to a character length | {{ title | truncate(40) }} |
len, length | Length | &:{{ tags | len }} |
join(separator) | Join an array | {{ tags | join(', ') }} |
first, last | First or last item | {{ tags | first }} |
int, float, string, list | Convert type | &:{{ qty | int }} |
round, abs, floor, ceil | Number rounding and sign | &:{{ price | round }} |
incr(by), decr(by) | Add or subtract | &:{{ score | incr(5) }} |
date_format(format) | Format a date | {{ created | date_format('MMM D, YYYY') }} |
date_shift(shift) | Shift a date | {{ $now() | date_shift('2days') }} |
to_currency(currency) | Currency text | {{ price | to_currency('EUR') }} |
to_percent(decimals) | Percentage text | {{ rate | to_percent(2) }} |
to_int, to_float | Convert to a number | &:{{ qty | to_int }} |
sha256 | SHA-256 hex digest | {{ email | sha256 }} |
b64_encode, url_encode | Encode text | {{ query | url_encode }} |
default(value) | Fallback for a missing field | {{ nickname | default('Guest') }} |
Common Patterns
Display Name
json
{
"display_name": "{{ $titlecase($trim(first_name ~ ' ' ~ last_name)) }}"
}Order Totals
json
{
"subtotal": "&:{{ price * quantity }}",
"tax": "&:{{ $round(price * quantity * 0.08) }}",
"is_free_shipping": "&:{{ price * quantity >= 50 }}"
}Expiration Date
json
{
"expires_at": "{{ $now() | date_shift('30days') }}"
}Content Preview
json
{
"preview": "{{ $truncate($strip_html(body), 25, '...') }}",
"word_count": "&:{{ $count_words($strip_html(body)) }}"
}Masking Sensitive Values
json
{
"card_display": "{{ $maskstr(card_last_digits, 4) }}",
"email_hash": "{{ $sha256($lowercase(email)) }}"
}Best Practices
- Use
&:for numbers and booleans.{{ }}always returns a string, so"{{ price * 2 }}"saves"24", not24. - Keep expressions short. Move complex logic to your application code.
- Don't build counters with templates. Template and operator writes read the document first, so concurrent writes can overlap.
Next Steps
- Data - Insert, update, and upsert documents
- Data Operators -
$incr,$push,$unset, and more - Query Syntax - Filter documents