How to read a template in yaml / the why and how of all those delimiters

This topic is part of the community-driven cookbook where you can find other topics you might be interested in or might like to contribute to.

When you ask around for a solution to a problem in Home Assistant, then 9 out of 10 times you’ll get a response using a Jinja2 template that you can put in a YAML file. These templates look like delimiter salad when you first see them.

While you can simply copy paste those templates in your configuration, things get way more fun if you can read them. It is also helpful understand why templates might fail if you change a seemingly little thing. And who knows, at some point you’ll be able to write your own templates. :slight_smile:

Below is a common example template. It is used to get the brightness from one light and use it for another light. This example and breakdown is borrowed from a question here:

  brightness: "{{ state_attr(trigger.entity_id, 'brightness') | int(0) }}"

Breaking down the template to character level:

  • Putting things inside {{ }} is the sign that we are switching from YAML to Jijna2 templates. Whenever you need to do something besides just entering a value, Jinja templates are your friend. Jinja is very related to Python. In Jinja this is a block that outputs something. Jinja also uses {% %} blocks that are not outputting something. They are mostly used for control blocks or setting Jinja variables. More on Jinja delimiters here.

  • Unfortunately, { } means something different in YAML when it is not in a string or multiline text block. When a template is on the same line as the tag then it must be inside quotes to avoid the confusion. That is why the " " are around it. If you enter a template in the GUI, do not use the outer quotes because then they will be put around the result.

  • When you need to use a quote inside " " you cannot simply use another " without ending the string. You either need to escape it, or switch to ' single quotes. Jinja and Python do not mind which one you use as long as they match. The first quote starts a string, the second one of the same kind ends it. So strings used for names use single quotes here. One really important thing: don’t use the fancy curly quotes! IOS keyboards can change them automatically, but they have no meaning to Python an Jinja and the difference will lead to errors.

  • Notice how 'brightness' has quotes but trigger.entity has none? Brightness is a literal name of an attribute, and trigger is a variable that holds an entity_id (That is also a variable inside the trigger object, the . delimiter in between selects it from the trigger object).
    This example was lifted from an automation. Automations provide all kinds of information on what triggered the automation via the trigger variable. So in this case, the entity_id that triggered the automation is used. If you wanted to explicitly name the entity_id yourself, you’d use something like 'light.myentity'. More on trigger variables here.

  • Now that we’re almost done with the delimiter salad, we can start doing stuff. But this is a good time to discuss indenting: YAML dislikes delimiters (some irony intended) and instead relies on indenting to decide what goes with what. Indenting identifies nested groups. Things with the same indent belong together on the same level, and when you indent deeper it means there is a deeper level of things inside that. The spaces on the beginning of a line are crucial information. More on YAML and indenting here.

  • To show what that all means: If you create a multiline text block in YAML using the > or the | symbol then you no longer need the outer quotes, because you are already in a string. But you do need an extra indent to show what belongs to the multiline block. You can add newlines for readability, as long as all lines are indented sufficiently. You could use double quotes for names if you wanted to (just a demo, normally I would not change it to keep things consistent). More on multiline strings here:

  brightness: >
    {{ 
      state_attr(trigger.entity_id, "brightness") | int(0) 
    }}
  • Soooo. Back to actually doing things in Jinja. state_attr() is a Python function from Home Assistant that you can use in Jinja to get the attribute of an entity. It is the preferred way because it has some error handling. What we need is the value of the ‘brightness’ attribute of the light entity that triggered the automation.

  • States of entities in Home Assistant are always strings. Attributes can be any type of value, but it is often hard to tell what it is exactly. The symbol | is called a pipe, it means use some filter to transform the value on the left. The filter used is int which converts the input to an integer value. It is advised to use int(0) which means, use 0 as a default if the input is not a number. When a light is off or unavailable, then the brightness attribute is not there. The 0 default will prevent errors.

Simple, right? :wink: :rofl:

The power of HA comes from the combination of many powerful languages, but at the price of complexity. Python and YAML like to keep things short, so there’s a lot of information in each character.

There are crazy number of things that templates can do using calculations, variables, functions and filters. A lot of it is regular Python or Jinja. Many Home Assistant specific things are listed in the template documentation.

This topic is part of the community-driven cookbook where you can find other topics you might be interested in or might like to contribute to.

5 Likes

the only thing I would add to this part is a clarification that the extra lines aren’t a requirement and are only added for additional clarification.

this:

  brightness: >
      {{ 
        state_attr("light.mylight", "brightness") | int(0) 
      }}

is exactly equivalent to this:

brightness: >
      {{ state_attr("light.mylight", "brightness") | int(0) }}

is exactly equivalent to this:

brightness: '{{ state_attr("light.mylight", "brightness") | int(0) }}'

the Jinja interpreter sees no difference between the three.

the magic sauce is the > (or | but I almost never use that notation) that tells the interpreter that everything on the next indented lines are all Jinja code.

Just to add additional clarification to this…

Jinja doesn’t care which style of quotes are used where. The most important thing is that the quote style used outside the Jinja template configuration (IOW, the thing that tells the interpreter that the next thing is a template and not just two curly braces) has to be different than the ones inside the template configuration.

so either of these will work exactly the same:

  brightness: "{{ state_attr('light.mylight', 'brightness') | int(0) }}"
  brightness: '{{ state_attr("light.mylight", "brightness") | int(0) }}'

it’s best if you establish a convention and always use it. That way you establish a habit so you are less likely to make mistakes. And if you do it’s easier to find them.

I always use double quotes outside the brackets and single quotes inside.

“match” means that you always need to be sure that you have a pair of quotes. you need an opening and a closing quote. Many template issues are from forgetting to close the quotes.

Luckily the template editor can help you there because it has color coded formatting displays to kind of tell you what it sees. If there is an error in a template look at the colors to help guide you.

Sometimes you will see entries inside of templates that don’t have quotes around them.

Unquoted entries tell the Jinja interpreter that you are referencing a Jinja variable created previously in the current template code block.

Quoted entries tell the interpreter that it’s a specific string and not a defined variable.

Here are examples.

These are all interpreted the same:

  brightness: "{{ state_attr('light.mylight', 'brightness') | int(0) }}"
  brightness: >
    {% set light = 'light.mylight' %}
    {{ state_attr(light, 'brightness') | int(0) }}
  brightness: >
    {% set light = 'light.mylight' %}
    {% set brightness_value = state_attr(light, 'brightness') | int(0) %}
    {{ brightness_value }}

@Edwin_D

I would have edited the above to add it but I wasn’t sure how you like it incorporated. So feel free to add it to the first post and I can come back and delete this post to keep everything cleaned up. Or not… :laughing:

2 Likes

Thanks for the clarifications and suggestions. I thnk the bit about variables is so important I spent another bullit on it :slight_smile: I took it out of the original example for simplification, only now to realise it fits in very much. So I put it back.

I also made some small changes to incorporate things about newlines. I even remembered to also warn about curly quotes. They really mess things up.

For the rest I think it is wise to keep your comments. Because the topic was getting longer and longer, I was afraid the reader would lose track of how it all ties together. So it is a tough chice for each bullit how many details should go in. That is why I tried to focus on the essence, and link to details if I knew where to find them.

And as for editing: it is a wiki item for a reason :slight_smile: That is what this community is for, right?

1 Like

Thx Edwin, great first try :wink:

you make life harder than necessary though, by using this brightness example, which uses (and requires) 2 things you dont explain later on.

  • ‘cast’ a string to an |int (what does ‘cast’ even mean in this context)
  • set a default for an |int (or any other manipulation that requires a default, link the Post by Petro for that matter)

my thoughts given the main objective of this cookbook post: use another example that does not require those 2

if you want to stick with it, I believe you should also add the explanation to the 2 requirements, as they are vital and essential to current templating in Home Assistant.

Lastly: personally I find the title, and especially the ‘salad’ aspect of it a bit too much.
Why not just state:

How to read and write a template in yaml / how to fulfill the delimiter requirements

or,

how to use the correct delimiters

it’s not as tongue-in-cheek, I am aware, but given things are the way they are, and no way around it, I believe the aspect of requirements should be lifted a bit more

Anyways, just my 2 cents on this fine morning, cheers!

1 Like

Thanks for the input.

I tried to explain the concept of filters in the last bullets. They happen a lot in templates.

I avoided the word cast. I used the words transform and convert, which are the best ways I know to explain what a filter does, in this case an int filter. The float filter is used the most, but it did not fit the example and an integer is a plain English word, floating point is less so. What is probably less clear is that I use the pipe symbol and move on to the concept of filters without explaining it. So it is a bullet about filters that starts off with naming the pipe. I’ll think a bit on how I can improve on it without it becoming longwinded.

Very true. I wanted to link to it but could not find it. Do you know where to find it?

These are the simplest forms of filters there are. You see them all the time, and they filters are one of the key things Jinja brings, but are hard to read and understand. So that is why I felt they should be here.

Not to mention I do not think you can get a more common example that uses all essential elements.

True. My feeble attempt at being funny isn’t in place here. Is the changed one better?

tbh, I missed that, sorry.
reading it now, I feel it falls short of being too helpful though, as it only scratches the surface. because of that, I believe you would better reference the post I link below, without explaining too much. It will only raise more answers.questions like this

this is what I meant Updating Templates with the new default values in 2021.10.x

well I do not agree with you there, and stressing that idea probably doesnt help… I honestly dont see what is difficult in seeing what a filter (or the function variant) does.

just state that for some operations the entities need to be of the correct type, and the type-casting filter/function takes care of that.

The default is required in the case the entity does not have an expected value (it is after all just a ‘string’ and you can not use a ‘string’ as a ‘number’,) or none at all.
The default is a safeguard to prevent the template output from erroring.

more details can be found in the etc etc…

sure, cool.

its just that this is not only about delimiters :wink:
it can be if you want it to be, and then there is a very small and succinct example of the specific brackets we use.

I believe Petro also posted that several times, so lets find that and re-post it here

btw, on the subject of default, I truly dislike it, as it in fact creates a fake/false output… it might be good to lightly suggest reading up on availability_templates, which imho are a better way of preventing erroneous output. (albeit making the entity unavailable in the case of trouble, which many users dislike)

choices…

Eh,

Those are not the same.

In regards to traditional output (You will see this in markdown card and other areas of HA):

brightness: "{{ state_attr('light.mylight', 'brightness') | int(0) }}"

will output

value

where these

  brightness: >
    {% set light = 'light.mylight' %}
    {{ state_attr(light, 'brightness') | int(0) }}

will output


value

and

  brightness: >
    {% set light = 'light.mylight' %}
    {% set brightness_value = state_attr(light, 'brightness') | int(0) %}
    {{ brightness_value }}

will output



value

The > is also a yaml identifier, and the > in yaml does not tell HA the rest of the lines are jinja. It tells HA: “The following indented lines are inside this field”. Carriage returns are retained, however the >- will remove the leading carriage return.

The only thing that lets HA know jinja is there is if {{ }}, {% %}, or {# #} are present in the string fed to it in the field.

In most places in HA, the - after the > or | does nothing because HA will trim leading and trailing white space on template results.

2 Likes

if you’d add a comment inside that brightness template, I believe Edwin would have all he needs to refer to in this topic

btw, why is your post no longer pinned in the Discord templates channel? I clicked it all the time to help other navigate there…

I tried to stick the most common things seen in templates, comments are not seen that much. But in the “more on” links each time I try to refer to a more verbose topic. So I do link to a post explaining that. One function, one filter, one explicit name, one variable, one style of multiline. Not too much, not too little. Is is by no means meant as a full guide on templates.

Which post?

I believe @Didgeridrew took most of my pins/posts and turned them into community guides. IIRC I asked him to do so because if I edit guides, I’m not sure if anyone else can edit them after.

The one I linked above on default

All pins seem to have been vanished though