Expressions

Expressions

Where Liquid gets awkward, write an expression: sums, comparisons, a choice between two values, and functions for numbers, dates and text.

Writing expressions

{% let name = expression %}

Assigns the value of an expression, as assign does: later Liquid and expressions read it, a for doesn’t hide it, a partial doesn’t see it. Liquid filters may follow the expression.

TakesWhat it is
namerequiredThe variable it assigns.
expressionrequiredOperators + - * / %, comparisons, and or not (or && || !), ? :, and the functions below. Reads any variable of the template.
{% let watts = cap.measure_power.value %}{% let kw = round(watts / 1000, 1) %}

{{= expression | filter }}

Writes the value of an expression, escaped like every other output. Liquid filters still work after it.

{{= cap.measure_power.value / 1000 | round: 1 }} kW{{= cap.onoff.value ? 'On' : 'Off' }}

{% if expression %}…{% elsif expression %}…{% endif %}

A condition Liquid can’t read itself is read as an expression, also in elsif and unless; every Liquid condition stays as it was. In an expression 0, empty text, null and false count as false (in Liquid only null and false do). Nothing is smaller or larger than null.

{% if until(settings.pickup, 'days') < 2 %}Soon{% else %}Later{% endif %}

Functions

Maths, dates and text. Every function is pure: it reads, never changes anything.

{% for item in list limit: 4 %}…{% else %}…{% endfor %}

Goes through a list one by one: a list setting (settings.alarms, a device or flow each), a buttons or values setting, any list a widget gets. else shows when the list is empty. Inside, forloop.index counts from 1 (forloop.index0 from 0), forloop.first and forloop.last say where it is, forloop.length how many there are. limit: and offset: take part of it. Outside a loop, list.size is how many, list.first and list.last one of them; an empty list is still true in a Liquid if, so test list.size > 0.

{% for alarm in settings.alarms %}  <p class="{% if alarm.cap.alarm_smoke.value %}alarm{% endif %}">{{ alarm.name }}: {{ alarm.cap.alarm_smoke.text }}</p>{% else %}  <p>No smoke detectors chosen.</p>{% endfor %}

round(x, decimals?)

Maths: A number rounded to 0–10 decimals (none unless given), halves away from zero; null when it isn’t a number.

{{= round(cap.measure_power.value / 1000, 1) }}

floor(x)

Maths: A number rounded down to a whole one; null when it isn’t a number.

{{= floor(cap.measure_battery.value / 25) }}

ceil(x)

Maths: A number rounded up to a whole one; null when it isn’t a number.

{{= ceil(until(t, "hours")) }}

abs(x)

Maths: A number without its minus sign; null when it isn’t a number.

{{= abs(cap.measure_power.value) }}

min(a, b, …)

Maths: The smallest of the numbers given (or of a list); what isn’t a number is skipped, null when none is.

{{= min(cap.measure_temperature.value, settings.limit) }}

max(a, b, …)

Maths: The largest of the numbers given (or of a list); what isn’t a number is skipped, null when none is.

{{= max(0, cap.measure_power.value) }}

clamp(x, lo, hi)

Maths: A number kept between lo and hi; a bound that isn’t a number doesn’t count. Null when x isn’t a number.

{{= clamp(cap.dim.value * 100, 5, 100) }}

since(time, 'minutes')

Dates: How long ago a time was, in seconds, minutes, hours or days, with fractions (negative for one to come). Takes ISO times, ISO dates and now.

{% if since(cap.alarm_motion.lastUpdated, 'minutes') < 5 %}Just now{% endif %}

until(time, 'days')

Dates: How long until a time, in seconds, minutes, hours or days, with fractions (negative once passed). A date without a time counts from the start of that day.

{% if until(t, 'days') < 2 %}soon{% else %}later{% endif %}

isBefore(a, b)

Dates: Whether time a is before time b; null when either isn’t a time.

{% if isBefore(now, '2026-12-24') %}Not yet{% endif %}

isAfter(a, b)

Dates: Whether time a is after time b; null when either isn’t a time.

{% if isAfter(cap.alarm_contact.lastUpdated, cap.onoff.lastUpdated) %}…{% endif %}

isToday(time)

Dates: Whether a time is on today’s date, on the screen’s clock; null when it isn’t a time.

{% if isToday(cap.alarm_generic.lastUpdated) %}Rang today{% endif %}

daysBetween(a, b)

Dates: Whole calendar days from a to b (yesterday to today is 1, whatever the hours; negative when b is earlier); null when either isn’t a time.

{{= daysBetween(now, settings.holiday) }} days to go

upper(text)

Text: Text in capitals; null or missing is empty text.

{{= upper(device.zone) }}

lower(text)

Text: Text in small letters; null or missing is empty text.

{{= lower(cap.thermostat_mode.value) }}

contains(textOrList, part)

Text: Whether a text has the other in it, or a list has the item; false otherwise.

{% if contains(device.name, 'Garden') %}…{% endif %}

startsWith(text, start)

Text: Whether a text starts with the other; false when it isn’t text.

{% if startsWith(cap.status_text.value, 'Error') %}…{% endif %}

length(x)

Text: Characters in a text, items in a list, keys in an object; 0 otherwise.

{% if length(settings.lines.items) > 1 %}…{% endif %}

format(x, '0.0')

Text: A number written with a pattern: zeros for digits always written ('00'), a comma for thousands ('#,##0'), decimals after the point ('0.00'). Null when it isn’t a number.

{{= format(cap.measure_temperature.value, '0.0') }}°