This guide's purpose is to serve as a growing list of advanced Liquid topics. If you're not familiar with Liquid, we recommend starting with our Liquid 101 guide.
Timezone Conversion
When developing Recipes that can be installed and used by a larger TRMNL audience, it's important to take the user's timezone into consideration, especially if your recipe is going to display date or time based information.
Dates & Times
Let's say a recipe uses an API that responds with ISO8601 dates like this: 2025-02-28T13:35:00Z
. If you simply pipe this into Liquid's date
filter, the user will see the date/time in its original UTC timezone.
To convert this date/time into the user's local timezone, we can leverage the variable trmnl.user.utc_offset
that TRMNL injects into every plugin. This variable holds the user's timezone offset in seconds, so we can use it like this:
{{ "2025-02-28T13:35:00Z" | date: "%s" | plus: trmnl.user.utc_offset | date: "%H:%M" }}
The first date filter with %s
converts the date/time into a unix epoch. We then add the user's timezone offset to that, and pipe it again to the date filter to format as we want.
Suppose the user's offset is -3 hours (-10800 seconds), this would display the time as 10:35
.
Dates Only
Be mindful when working with date-only cases like 2025-02-28
. When you pipe this into Liquid's date filter, it will assume the time is 00:00:00
. If you apply the previous concept to this case, a user who is behind UTC is going to see a date the day before. This is because 2025-02-28T00:00:00
in UTC will convert to 2025-02-27T21:00:00
in UTC-3 (for example).
You'll need to analyze this on a case by case basis to decide if the conversion is relevant or if it's going to confuse the end-user who installs your plugin.
Last updated/refreshed at
For some recipes it may be interesting to display the time at which the last update/refresh occurred. Liquid's date filter supports the special keyword now
to represent the current date/time. We can apply what we learned before to also show this date/time in the user's timezone:
<p>Updated at {{ "now" | date: "%s" | plus: trmnl.user.utc_offset | date: "%H:%M" }}</p>
Random Numbers
Liquid does not have native filters to generate random numbers, but fortunately we can adapt the date
and modulo
filters for this purpose!
Simple random number
To generate a simple random number between 0 and 9:
{% assign random_number = "now" | date: "%N" | modulo: 10 %}
This is useful when you want to select a random element from a collection, as you could do:
{%liquid
assign rand_index = "now" | date: "%N" | modulo: my_collection.size
assign rand_item = my_collection[rand_index]
%}
Random between an interval
Here's a more complex example that generates a random number between two other numbers, with both of them included.
{%liquid
# random number between 5 and 10
assign min_range = 5
assign max_range = 10
assign range = max_range | minus: min_range | plus: 1
assign random_number = "now" | date: "%N" | modulo: range | plus: min_range
%}
Type Conversion
Liquid is capable of doing some magic type conversion between numbers and strings behind the scenes.
Let's say you have the number 1.5
but you want to display it using a comma as the separator. One would think that the replace
filter only works on strings, but it actually works on numbers too:
{{ 1.5 | replace: ".", "," }}
Decimal Numbers
If you try the code below you'll be surprised to see the number 0
displayed as a result:
{{ 1 | divided_by: 2 }}
This is because both numbers are integers, so Liquid also outputs the result as an integer. When you need the result to be a decimal number, simply "force" the divisor or dividend to also be a decimal number. For example:
{{ 1.0 | divided_by: unknown_number }}
This way, if unknown_number
was actually the integer 2
, the result would be correctly displayed as 0.5
.
Left Padding Strings
Sometimes you may need to left-pad strings with other characters, such as zeroes or spaces. For example, let's say you're converting a number of seconds into hours and minutes to display as as hh:mm
. You do all the math and end up with something like this: 1:3
. Wouldn't it make more sense to display it as 01:03
?
Using the prepend
and slice
filters we can achieve this result:
β
{%liquid
# original values (suppose these were calculated)
assign hours = 1
assign minutes = 3
# left-padding with zero
assign hours = hours | prepend: "00" | slice: -2, 2
assign minutes = minutes | prepend: "00" | slice: -2, 2
%}
You can modify this logic by changing the padding characters in the prepend
filter and adjusting the lengths accordingly in the slice
filter.
Mixing Liquid and JavaScript
One commonly overlooked concept when developing TRMNL plugins is that you can actually mix Liquid and JavaScript together. There are many use cases for this, but a very common one is building Charts using data returned from an API.
Mixing both worlds is just a matter of using Liquid syntax inside JS code:
<script>
console.log("This value comes from Liquid: {{ liquid_variable }}");
</script>
When mixing Liquid and JS, we need to pay attention to the data types of the Liquid variables, as not all of them translate directly to cognate JS data types.
Strings
When you have a Liquid string that you want to use as a JS string, don't forget to enclose it in quotes:
<script>
const js_string = "{{ liquid_string }}";
</script>
Numbers and Booleans
Numbers and Booleans are the only Liquid data types that convert naturally to JS:
<script>
const js_number = {{ liquid_number }};
const js_boolean = {{ liquid_boolean }};
</script>
Lists/Arrays
Lists are the most controversial case as you cannot directly assign a Liquid list to a JS array like the previous examples. You need to iterate through the Liquid list values and push each one of them to the JS array:
<script>
// initialize the JS array
const js_array = [];
{% for item in liquid_list %}
js_array.push({{ item }});
{% endfor %}
console.log(js_array);
</script>
You'll come across this pattern when creating arrays to be used for a chart's series data.
Objects
Liquid objects also do not translate directly to JS objects. Fortunately, TRMNL has implemented a custom json
filter that does exactly this:
<script>
const js_object = {{ liquid_object | json }};
console.log(js_object.property);
</script>
Be sure to check out the resources below for more TRMNL custom filters.
More Resources
TRMNL Custom Liquid Filters: a growing list of custom Liquid filters developed by and available only on TRMNL.