Template syntax
Templates are made of a handful of pieces that show up everywhere. Once you’ve seen them a few times, you will recognize them instantly and templates will stop looking like gibberish. This page introduces each piece one at a time.
Don’t try to memorize everything at once. Read through, keep this page open while you experiment in the template editor, and come back when you need a refresher. That is honestly how most people learn this.
Code lives inside markers
When Home Assistant reads a template, it gives you back the regular text exactly as you wrote it. But when it sees certain markers, it knows the text in between is code that needs to be calculated. Those markers are called delimiters (a fancy word for “the thing that marks where something starts and stops”).
Templates have three delimiters. You will use the first one most of the time:
-
{{ ... }}says “calculate this and show me the result”. -
{% ... %}says “run this logic, but don’t add anything to the output directly”. -
{# ... #}says “this is a note for me, ignore it”.
Here is all three working together:
{# Say hello to whoever is home #}
Hello, {{ states('person.frenck') }}.
{% if is_state('sun.sun', 'below_horizon') %}
It is dark outside.
{% endif %}
Hello, home.
It is dark outside.
Notice what happened:
- The comment (
{# ... #}) was removed from the output. - The
{{ states('person.frenck') }}was replaced with the person’s current state. - The
{% if ... %}decided whether to include the “It is dark outside.” line.
That is the whole trick. Everything else on this page is details about what you can put inside those markers.
Expressions: anything that produces a value
When you write {{ ... }}, whatever goes inside is called an expression. An expression is “something that can be turned into a value”. The value could be a number, a piece of text, a list, or anything else.
These are all valid expressions:
- Text:
'hello'or"hello"(both kinds of quotes work). - Numbers:
42,3.14. - True and false:
true,false. - Nothing at all:
none. - A list of things:
[1, 2, 3]or['kitchen', 'bedroom']. - A calculation:
10 + 5. - A call to a function:
states('sensor.temperature')ornow(). - A variable you defined earlier (more on those in loops and conditions).
When a value has parts inside it (like a state that carries attributes), you reach them with a dot or with square brackets:
Temperature: {{ states.sensor.outdoor.state }}
Friendly name: {{ states.sensor.outdoor.attributes.friendly_name }}
Same thing: {{ states.sensor.outdoor.attributes['friendly_name'] }}
Temperature: 22.5
Friendly name: Outdoor temperature
Same thing: Outdoor temperature
Dots look cleaner. Brackets are needed when the name has a space or other character that dots don’t handle. The most common place you’ll hit this is with entity IDs that start with a number, like device_tracker.2008_gmc. Writing states.device_tracker.2008_gmc fails because names cannot start with a digit, so use states.device_tracker['2008_gmc'] instead.
Operators: doing things with values
An operator is a symbol that combines or compares values. You already know these from arithmetic at school. They work exactly the same here.
Math
Addition: {{ 10 + 5 }}
Subtraction: {{ 10 - 5 }}
Multiplication: {{ 10 * 5 }}
Division: {{ 10 / 3 }}
Integer division: {{ 10 // 3 }}
Remainder: {{ 10 % 3 }}
Power: {{ 10 ** 2 }}
Addition: 15
Subtraction: 5
Multiplication: 50
Division: 3.3333333333333335
Integer division: 3
Remainder: 1
Power: 100
The last three are less common. // is division that throws away the decimals (so 10 // 3 is 3, not 3.33). % gives you what is left over after division. ** raises one number to the power of another.
Comparison
Comparison operators ask “how does this value relate to that one?”. They always answer with True or False.
Equal: {{ 10 == 10 }}
Not equal: {{ 10 != 5 }}
Greater than: {{ 10 > 5 }}
Less than or equal: {{ 5 <= 5 }}
Equal: True
Not equal: True
Greater than: True
Less than or equal: True
Watch out for the double equals in ==. A single = is used for assignment (giving a name to a value), while == is the question “are these two the same?”.
Logic
Logic operators combine multiple true-or-false answers into one. and needs both to be true. or needs at least one. not flips the answer. in checks whether something is inside a list or a piece of text.
Both true: {{ true and false }}
Either true: {{ true or false }}
Flipped: {{ not false }}
Contains: {{ 'bedroom' in 'bedroom light' }}
Both true: False
Either true: True
Flipped: True
Contains: True
Filters: changing a value
A filter takes a value and transforms it into something new. Filters use the | symbol (called a pipe). The pipe points at the filter, like handing the value over to it.
You can read a filter chain out loud left to right: “take hello, make it upper case” or “take these numbers, sort them, then join them with commas”.
Upper case: {{ 'hello' | upper }}
Rounded: {{ 3.14159 | round(2) }}
Chained: {{ [3, 1, 2] | sort | join(', ') }}
Upper case: HELLO
Rounded: 3.14
Chained: 1, 2, 3
Home Assistant has dozens of filters for converting between types, formatting text, calculating with numbers, and working with lists. The template functions reference lists them all, each with examples.
Many filters are also functions
A lot of Home Assistant’s template functions can be used either as a filter (with |) or as a regular function call. These two are exactly the same:
As a function: {{ float(states('sensor.outdoor_temperature')) }}
As a filter: {{ states('sensor.outdoor_temperature') | float }}
As a function: 22.5
As a filter: 22.5
The filter form reads more naturally when you are chaining several steps together. The function form can be clearer when you need an explicit fallback: float(value, 0) versus value | float(0). Use whichever feels more readable in each situation. The reference page for each function notes which forms it supports.
Watch out: filters run before math
Here is a gotcha that catches everyone at least once. The | symbol binds tighter than +, -, *, /, and all the other math operators. That means this template does not do what it looks like it should:
{{ 10 / 10 | round(2) }}
1
You might read that as “ten divided by ten, rounded to two decimals”, which should be 1.0. But because filters take priority, it actually runs as “ten divided by (ten rounded to two decimals)”, which is 10 / 10.0 = 1.0… Wait, that’s also 1.0. Let me try a clearer example:
{{ 20 - 5 | round(0) }}
15
This one also happens to work. The gotcha bites hardest when the filter changes the value meaningfully. When in doubt, add parentheses so the order you want is clear:
With parentheses: {{ (10 / 3) | round(2) }}
Without parentheses: {{ 10 / 3 | round(2) }}
With parentheses: 3.33
Without parentheses: 3.3333333333333335
In the “without parentheses” version, 3 | round(2) runs first (rounding 3 gives 3), then 10 / 3 divides as normal. The rounding had no effect. Parentheses force 10 / 3 to happen first, then round.
Whenever a template looks right but gives an unexpected result, suspect operator precedence and reach for parentheses.
Tests: asking questions about a value
A test is a yes-or-no question you ask about a value. Tests are written with the word is, which reads naturally.
Is a number: {{ 42 is number }}
Is text: {{ 'hello' is string }}
Is even: {{ 6 is even }}
Is in a list: {{ 3 is in [1, 2, 3] }}
Is a number: True
Is text: True
Is even: True
Is in a list: True
To ask the opposite, write is not:
{{ 42 is not number }}
False
Tests are most useful inside if statements, where you want to react differently based on what kind of value you have.
Whitespace: trimming unwanted spaces
Templates keep every space and newline you write. Sometimes that is fine. Other times, all those extra line breaks from {% if ... %} blocks end up in the output and look messy.
Adding a - inside a marker trims the whitespace on that side. So {%- ... -%} removes spaces and line breaks both before and after the marker.
Before
{%- if true -%}
middle
{%- endif -%}
After
BeforemiddleAfter
Without those dashes, the output would have line breaks around “middle”. Whitespace control matters most when a template has to fit on a single line, like a notification title, or when the spacing bothers you.
Putting it all together
Most real templates mix several of these pieces. Here is one you might see in an automation:
{% set temp = states('sensor.outdoor_temperature') | float(0) %}
It is {{ temp | round(1) }}°C outside,
which is {{ 'warm' if temp > 20 else 'cool' }}.
It is 22.5°C outside, which is warm.
Let’s read it piece by piece:
-
{% set temp = ... | float(0) %}reads the outdoor temperature, converts it to a number (with0as a fallback in case the sensor is offline), and stores the result in a variable namedtemp. -
{{ temp | round(1) }}shows that number rounded to one decimal place. -
{{ 'warm' if temp > 20 else 'cool' }}picks between two words. Iftempis more than 20, it picks “warm”; otherwise, “cool”.
If that feels like a lot, read the Loops and conditions page next. It explains set, if, and for in detail.
Next steps
- Learn about loops and conditions to make your templates smarter.
- Writing templates inside automations or scripts? Read Templates in YAML for the quoting rules.
- Browse the template functions reference for the full list of filters, tests, and functions.
- Try every example on this page in the template editor. Experimenting is the fastest way to learn.