Types and type conversion
Templates work with several kinds of values: text, numbers, lists, and a few others. Each kind is called a type, and knowing which type you have matters because most operations only work with specific types. Trying to do math on text, or count items in an iterable, gives you surprising results until you convert first.
This page walks through the types you will meet, how they interact, and when you need to convert between them.
The types you will meet
-
Text (
strorstring): a piece of text, written in single or double quotes. Every entity state in Home Assistant is stored as text. -
Integer (
intorinteger): a whole number without a decimal point. Examples:0,42,-7. -
Float (
float): a number with a decimal point. Examples:3.14,-0.5,22.0. -
Boolean (
boolorboolean): eitherTrueorFalse. The answer to yes-or-no questions. -
None (
NoneTypeorNone): the absence of a value. Returned when something does not exist. -
List: an ordered collection of values, written with square brackets. Example:
[1, 2, 3]or['kitchen', 'bedroom']. Also called an array or sequence. -
Dictionary (
dict): a set of key/value pairs, written with curly braces. Example:{'temp': 22, 'humidity': 54}. Also called a map or mapping. -
Iterable: a sequence of values you can loop over once. Also called a generator. Filters like
map,select, andselectattrreturn iterables. You cannot count them, index them, or use them twice until you turn them into a list. Covered in detail below. -
Datetime: a moment in time with a date, time, and time zone. Returned by
now()and thelast_changedattribute on states.
Why types matter
Most functions and operators only work with specific types. Here are some examples:
- Math operators (
+,-,*,/) need numbers. Using them on text fails. -
| countor| lengthneeds a list or similar countable. It does not work on an iterable. - Comparison with
<,>works on numbers correctly, but on text it compares alphabetically ('6' > '10'isTruebecause'6'comes after'1'). - The
inoperator works on lists, text, and dicts, but the behavior is different for each.
When a template produces an unexpected result, the type of the values involved is usually the first thing to check.
Every state is text
This is the single most important type fact in Home Assistant templates: every entity state is stored as text. Even when a sensor looks like it reports a number, states('sensor.temperature') returns the text '22.5', not the number 22.5.
{{ states('sensor.temperature') | typeof }}
str
This is why you see | float(0) or | int(0) everywhere in template examples. Before you can do math, compare numerically, or average a bunch of values, you have to convert them to numbers.
{# This looks like math but is text concatenation #}
{{ states('sensor.a') + states('sensor.b') }}
22.518.5
{# This does real math #}
{{ states('sensor.a') | float(0) + states('sensor.b') | float(0) }}
41.0
Converting between types
Each type has a matching filter (and function) to convert a value to that type:
-
| float(default): converts to a decimal number. If the value cannot be converted, the default is used. -
| int(default): converts to a whole number. Decimals are dropped. -
| string: converts anything to text. -
| bool(default): converts toTrueorFalse. -
| list: materializes an iterable into a list.
The default arguments for float, int, and bool are fallbacks used when the conversion fails (for example, a sensor that reports unavailable).
{{ "3.14" | float(0) }}
{{ "seven" | float(0) }}
{{ "42" | int(0) }}
{{ 3.7 | int }}
{{ 1 | string }}
{{ "yes" | bool }}
3.14
0.0
42
3
1
True
Rule of thumb: whenever you read a state and need to do math with it, add | float(0) or | int(0) with a sensible fallback.
Iterables look like lists, but are not
Some filters return an iterable instead of a list. An iterable is a lazy sequence. It gives you values one at a time when you loop over it, but it is not a list. You cannot count it, index it, or use it twice.
The Home Assistant filters that return iterables are:
If you try to count an iterable directly, you get an error:
{# This fails #}
{{ states.light | selectattr('state', 'eq', 'on') | count }}
Error: object of type 'generator' has no len()
The fix is to add | list to turn the iterable into a list first:
{# This works #}
{{ states.light | selectattr('state', 'eq', 'on') | list | count }}
3
You will see | list at the end of filter chains all over Home Assistant templates. It is not optional decoration, it is the step that makes the result countable, sortable, and reusable. A good habit: if you feed the result of map, select, reject, selectattr, or rejectattr into anything that needs a list, add | list.
Getting items from lists and dictionaries
Collections give you several ways to reach the items inside them.
From a list
Use the first and last filters to grab the ends, or square brackets with an index to reach a specific position. Indices start at 0, and negative numbers count from the end.
{% set rooms = ['kitchen', 'bedroom', 'garage'] %}
First: {{ rooms | first }}
Last: {{ rooms | last }}
Index 0: {{ rooms[0] }}
Index 1: {{ rooms[1] }}
Index -1: {{ rooms[-1] }}
First: kitchen
Last: garage
Index 0: kitchen
Index 1: bedroom
Index -1: garage
From a dictionary
Dictionaries support two ways to read a value: bracket notation (data['key']) and dot notation (data.key). Both usually work.
{% set data = {'temp': 22.5, 'humidity': 54} %}
Bracket: {{ data['temp'] }}
Dot: {{ data.temp }}
Bracket: 22.5
Dot: 22.5
Use bracket notation when a key name could conflict with a dict method. If a key is named values, keys, items, get, or any other built-in dict method, dot notation returns the method instead of your value. This is common with API responses.
{% set response = {'status': 'ok', 'values': [1, 2, 3]} %}
{{ response['values'] }}
[1, 2, 3]
When in doubt, reach for bracket notation. It always looks up the dictionary value first.
Checking what type you have
When you are not sure what type a value is, use typeof to inspect it:
{{ 42 | typeof }}
{{ 3.14 | typeof }}
{{ "hello" | typeof }}
{{ [1, 2, 3] | typeof }}
{{ {"a": 1} | typeof }}
{{ None | typeof }}
int
float
str
list
dict
NoneType
The names you get back are the Python type names: str for text, int for whole numbers, float for decimals, bool for booleans, list, dict, NoneType for None.
Testing types with tests
Tests let you check a type inside an if condition, without calling typeof:
{{ 42 is number }}
{{ "hello" is string }}
{{ [1, 2, 3] is iterable }}
{{ {"a": 1} is mapping }}
{{ None is none }}
True
True
True
True
True
Useful type tests: number, string, boolean, integer, float, iterable, mapping, none, defined. See the Comparison category in the reference for the full list.
Common type mistakes
-
Doing math on a sensor state without
| float(0). Always convert first. -
Comparing text to a number.
states('sensor.temp') < 20compares text to a number. Always| float(0)first. -
Using
<or>on text. Text compares alphabetically, which rarely does what you want for numbers. -
Forgetting
| listaftermap,select,reject,selectattr,rejectattr. The result is an iterable, not a list, so| countand| lengthfail. -
Expecting
Noneorunknownto be a number. Both break math operations. Use a fallback.
Next steps
- For hands-on debugging, see Debugging templates.
- For more on working with states, see Working with states.
- For recipes that use these conversions, see Common template patterns.