Templates in Adafruit IO Actions are done using the Liquid templating language. It has built in support for some helper functions, or "filters" as they are known in liquid parlance.
All template blocks require a child block to be attached, with either a text string block, or paragraph block, or something else text compatible (like a Get Feed value block).
Every line in the template gets whitespace (line-breaks) at the end of it, but that can be prevented by adding the dash (-) syntax to any placeholders ({{- placeholder -}}) or commands ({%- assign t = vars.now -%}).
It can be used to trim / collapse the whitespace from just the beginning, or end, or both. For example {{ vars.now }} prints the time with a newline afterwards, but using {{- vars.now -}} removes whitespace before and afterwards. Alternatively {{ vars.now -}} would just remove from the end.
Using feed values as part of a template allows you to easily add units and symbols, or display messages, to dramatically improve the user experience. Templates can be used in place of text values for any of the blocks.
The classic example is to add a prefix and suffix for a dashboard label, or sending an email report, to yourself, based on some triggering condition (reacting to incoming data) or schedule.
There are a few built-in Adafruit IO custom template "placeholders". These allow easy insertion of the user related properties (like name) using {{ user }}, variables created inside the action using {{ vars }}, and any feeds used in the action under {{ feeds }}. Of course the variable must be set before use in a template.
Accessing the sub-properties of these placeholders is done with the dot-notation, e.g. {{ user.name }}, or square bracket indexing / accessors like python uses, e.g. {{ user["name"] }}.
Bracket notation is preferred, as it supports any properties with unusual characters (like dots . and spaces).
For the feeds placeholder, you access each feed as a sub-property using the feed key (dash separated lowercase), e.g. a feed called Feed-Key in a group named Group-Key would be feeds["group-key.feed-key"]
Then you want to access the sub-property .value, or another like name, last_value, updated_at, etc.
e.g. {{ feeds["group-key.feed-key"].value }} would be replaced with the feed value inside a template.
The feed key can be found using the URL when visiting the feed page, or using the feed info button, or lastly by logging/emailing the value of {{ feeds }}. See this guide on Editing Feeds for details on feed keys:
https://learn.adafruit.com/adafruit-io-basics-feeds/editing-a-feed
With templates referring to feed values the syntax can become very long and almost unreadable, but you can help yourself by creating variables first from the needed feeds, and then refer to the variables instead.
Using Filters and basic logic in Liquid templates
Spend a few minutes reading about basic usage and the list of filters on the official Liquid help documentation:
The template uses a couple of filters to do some basic arithmetic (plus:), repeating characters using for-loops (based on the counter value), and printing phrases of encouragement based on certain if conditions.
Filters are quite simple at their most basic usage level, and chained together using Pipes (vertical bar |), for example to make your name a lowercase version: {{ user.name | downcase }} [Docs for downcase]
The idea for this template was to repeat an emoji the same number of times as the counter, as a sort of bar graph / progress indicator. With a small nearly-identical section of the email template for each of the counters.
Finally the template totals all the events recorded by the counters, along with a hypothetical success rate. Then based on some predefined ranges, gives an appropriate summary and phrase of encouragement. Of course after the daily report is sent the counters should be reset to zero.
Some of these might be clearer if calculated as variables first, like total events, and then used in the template.
The example template calculates everything in the template, unwisely, just to show how it can be done.
If you've not read the previous pages, and seen the template contents, then know that there are 4 counters stored in feeds. Those feed values have been placed into Variable blocks, and then a daily summary report is emailed, composed from the sections below.
The first piece of the template matching the counter for button 1 (did a wee) has two simple bits, print the value, then repeat emoji X times.
🚽 Successful Wees: {{ vars["wee_progress"] }}
{% for i in (1..(vars["wee_progress"])) %}💧{% endfor %}
Here you can probably tell that the first line outputs the current value of the variable, and the second line repeats the character 💧 up to the number of times in the variable's value with a "for i = 1 to X, repeat💧".
If you want to have a summary phrase in each section of the report, then you can add lines like these too:
{% if vars.wee_progress == 0 %}
Slow progress is still progress, keep that encouraging reinforcement going!
{% elsif vars.wee_progress <= 2 %}
Getting the hang of this, keep up the amazing work! 🎯
{% else %}
SUPERSTAR ALERT! 🌟 Absolutely crushing it! You've got a potty champion! 🏆
{% endif %}
This section is then repeated for the remaining progress variables, with slightly different phrases & thresholds.
You'll no doubt notice that the template is doing numeric comparison of the variables (that you added zero to).
All feed data on Adafruit IO is stored as strings (text encased in speech marks). We previously did a bit of automatic conversion in some places, but now with the new Actions we leave the power (and complexity) in your hands...
Having all data as text strings is normally fine to use in templates, however if doing comparisons it gets tricky, but there are a couple of methods for converting (casting) text data to numerical data instead.
Firstly, Maths blocks in the toolbox allow you to manipulate numbers, automatically casting strings, so use + 0.
Secondly, liquid templates support their own form of variables, both capturing and assigning, using the syntax {% capture name %}value{% endcapture %}(captured as strings), and {% assign name = value %}. Some maths filters convert text strings automatically, like plus:, as does the range syntax (1..number) used in the template.
This template uses capture for a few extra template variables, calculating the total tries versus successes, creating a percentage bar of success from repeating ASCII greyscale characters.
Then finally, the total_events is divided by 3 and the remainder then assigned to event_mod using | modulo: 3. The last phrase is conditional based on the remainder in event_mod, a bit like "Quote of the Day".
Now imagine if you will, saving the value of total_events and successes, to new feeds, using Variables for the templated values, then the Set Feed block. You could even have total_events_last_week, 2weeks ago, etc.
Your report could include your sensor data or weather data too, maybe that dramatically impacts productivity...
Then with weekly, monthly, etc, hopefully your mind also begins to play with other non potty ideas...
Remember that you get all sorts of benefits with IO Plus, like unlimited feeds and devices! And don't forget to checkout the Weather block, exposing the full Apple® WeatherKit data for your added locations.
A Real Liquid Template Debugger
If you wish to experiment with liquid templates in a more constructive environment, then try this online liquid template editor and debugger (I've included the variables with the template for easier testing):
Templates are so new that we're still getting the error messaging and validation sorted, so for now if your template has a serious error then it just won't product any output (no new entry in Previous Runs).
Try breaking it down into smaller template chunks for testing, and assign the templates to variables or use log blocks in combination with the Run button (but mind the 5 second cooldown).
If it's valid in the liquid template editor above then it probably should work so tell us if it doesn't!
Page last edited June 17, 2025
Text editor powered by tinymce.