Skip to main content
All CollectionsPlugins
Advanced Liquid
Advanced Liquid

Pro tips for TRMNL plugin templating.

Ryan avatar
Written by Ryan
Updated over 3 weeks ago

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

Did this answer your question?