What Youβll Learn in This Module
Loops let you repeat a block of content once for each item in an array. Instead of writing separate lines for Feature 1, Feature 2, Feature 3, you write one template and Liquid repeats it for every item in the list. This module covers the for loop, its control parameters (limit, offset), and the forloop helper variables that let you customize output based on position.
What Is a Loop?
A loop is an instruction that says: βFor each item in this list, do the following.β It takes a template (the body of the loop) and repeats it once per item, substituting the current item each time.
Excel analogy: Imagine you have a column of feature names (A1:A10) and you want to create a numbered list in column B. Youβd write a formula in B1 like
=ROW()&". "&A1, then copy it down to B10. Aforloop does the same thing β it takes one template and applies it to every item automatically.
Why it matters in Cast
Customer data often includes lists β features used, open tasks, products purchased, regions served. Loops let you display these lists dynamically, adapting to however many items each customer has. A customer with 3 features gets a 3-item list; one with 7 gets a 7-item list β all from the same template.
Basic for Loop
Liquid syntax
{% for item in array %}
{{ item }}
{% endfor %}
itemis a temporary variable name β you choose it. It holds the current item on each pass through the loop.arrayis the array to loop through.- Everything between
{% for %}and{% endfor %}is repeated once per item.
Real Cast example
{%- assign features = feature_list | split: "," -%}
Your active features:
{% for feature in features %}
- {{ feature | strip }}
{% endfor %}
If feature_list is "Analytics,Reporting,Alerts,Dashboards":
Your active features:
- Analytics
- Reporting
- Alerts
- Dashboards
Numbered Lists with forloop.index
Inside a loop, Liquid provides a special object called forloop with information about the current iteration:
| Variable | What It Returns | First Item | Second Item | Last Item (of 4) |
|---|---|---|---|---|
forloop.index | Position starting at 1 | 1 | 2 | 4 |
forloop.index0 | Position starting at 0 | 0 | 1 | 3 |
forloop.first | Is this the first item? | true | false | false |
forloop.last | Is this the last item? | false | false | true |
forloop.length | Total number of items | 4 | 4 | 4 |
Real Cast example β numbered list
{%- assign features = feature_list | split: "," -%}
Your top features this quarter:
{% for feature in features %}
{{ forloop.index }}. {{ feature | strip }}
{% endfor %}
Output:
Your top features this quarter:
1. Analytics
2. Reporting
3. Alerts
4. Dashboards
Real Cast example β comma-separated with βandβ before the last item
{%- assign features = feature_list | split: "," -%}
Your features include
{%- for feature in features -%}
{%- if forloop.last and forloop.length > 1 %} and {% elsif forloop.first == false %}, {% endif -%}
{{ feature | strip }}
{%- endfor -%}.
If there are 3 features: Your features include Analytics, Reporting and Alerts.
This uses forloop.last to insert βandβ before the final item and forloop.first to avoid a leading comma.
Limiting Results with limit
The limit parameter stops the loop after a specified number of iterations. Useful when you want to show βtop 3β or βfirst 5β from a longer list.
{% for feature in features limit: 3 %}
{{ forloop.index }}. {{ feature | strip }}
{% endfor %}
Even if features has 10 items, only the first 3 are shown.
Real Cast example β top 3 with overflow count
{%- assign features = feature_list | split: "," -%}
Your top features this quarter:
{% for feature in features limit: 3 %}
{{ forloop.index }}. {{ feature | strip }}
{% endfor %}
{% if features.size > 3 %}
...and {{ features.size | minus: 3 }} more.
{% endif %}
Output (for 6 features):
Your top features this quarter:
1. Analytics
2. Reporting
3. Alerts
...and 3 more.
Skipping Items with offset
The offset parameter skips a specified number of items from the beginning:
{% for feature in features offset: 2 %}
{{ feature | strip }}
{% endfor %}
This skips the first 2 items and starts from the third.
Combining limit and offset
You can use both together to get a βsliceβ of the array:
{% for feature in features limit: 3 offset: 2 %}
{{ feature | strip }}
{% endfor %}
This skips 2 items, then shows the next 3 β items at positions 3, 4, and 5.
Conditional Content Inside Loops
You can use if/else inside loops to customize each itemβs display based on its value or position:
{%- assign tasks = open_tasks | split: "," -%}
{% for task in tasks %}
{%- assign clean_task = task | strip -%}
{% if forloop.first %}
π΄ Priority: {{ clean_task }}
{% else %}
βͺ {{ clean_task }}
{% endif %}
{% endfor %}
Output:
π΄ Priority: Renew contract
βͺ Schedule QBR
βͺ Update health score
Empty Arrays β The for/else Pattern
What if the array is empty? You can handle this with an else clause directly inside the for block β this else runs only if the array has zero items:
{%- assign features = feature_list | default: "" | split: "," -%}
{% for feature in features %}
{{ forloop.index }}. {{ feature | strip }}
{% else %}
No features are currently active on your account.
{% endfor %}
If feature_list is empty, the else content is shown instead of the loop body.
π‘ This is cleaner than wrapping the whole thing in {% if features.size > 0 %}.
Nested Loops
You can put loops inside other loops, though this is less common in Cast narrations:
{%- assign categories = "Features,Integrations" | split: "," -%}
{%- assign feature_items = "Analytics,Reporting" | split: "," -%}
{%- assign integration_items = "Salesforce,Slack,Jira" | split: "," -%}
{% for category in categories %}
{{ category }}:
{% if category == "Features" %}
{% for item in feature_items %}
- {{ item }}
{% endfor %}
{% else %}
{% for item in integration_items %}
- {{ item }}
{% endfor %}
{% endif %}
{% endfor %}
β οΈ Keep nesting to one level when possible. Deeply nested loops are hard to read and maintain.
Whitespace Control in Loops
Loops can produce a lot of unwanted blank lines. Use whitespace control (-) to keep output clean:
Without whitespace control:
{% for feature in features %}
{{ forloop.index }}. {{ feature | strip }}
{% endfor %}
Produces blank lines around each item.
With whitespace control:
{%- for feature in features %}
{{ forloop.index }}. {{ feature | strip }}
{%- endfor %}
Cleaner output with no extra blank lines.
π‘ Experiment with whitespace control placement to get the exact spacing you want. The - on {%- for %} strips the blank line before each item; the - on {%- endfor %} strips the blank line after the last item.
Common Mistakes
β Looping over a string instead of an array:
{% for item in feature_list %}
{{ item }}
{% endfor %}
If feature_list is a comma-separated string (not an array), the loop iterates over individual characters.
β Split first:
{%- assign features = feature_list | split: "," -%}
{% for feature in features %}
{{ feature | strip }}
{% endfor %}
β Forgetting endfor:
{% for feature in features %}
{{ feature }}
β
Every for needs an endfor:
{% for feature in features %}
{{ feature }}
{% endfor %}
β Using the wrong variable name inside the loop:
{% for feature in features %}
{{ item | strip }} β should be "feature", not "item"
{% endfor %}
β
Use the variable name you declared in the for tag:
{% for feature in features %}
{{ feature | strip }}
{% endfor %}
Try It Yourself
Exercise: You have open_tasks with the value "Renew contract,Schedule QBR,Update health score,Review usage data,Prepare expansion proposal". Write Liquid that:
- Splits it into an array
- Shows the total count of open tasks
- Lists the first 3 as a numbered list
- If there are more than 3, shows a message ββ¦and X more tasks to addressβ
- If the list is empty, shows βNo open tasks β great job!β
Click to reveal the answer
```liquid {%- assign tasks = open_tasks | default: "" | split: "," -%} {% if tasks.size > 0 %} You have {{ tasks.size }} open tasks: {%- for task in tasks limit: 3 %} {{ forloop.index }}. {{ task | strip }} {%- endfor %} {% if tasks.size > 3 %} ...and {{ tasks.size | minus: 3 }} more tasks to address. {% endif %} {% else %} No open tasks β great job! {% endif %} ``` Output: ``` You have 5 open tasks: 1. Renew contract 2. Schedule QBR 3. Update health score ...and 2 more tasks to address. ``` Key details: - `default: ""` handles a nil field (empty split produces an empty array) - `tasks.size > 0` check wraps the entire loop for the empty case - `limit: 3` restricts the numbered display - `tasks.size | minus: 3` calculates the overflow countWhatβs Next
In Module 15, youβll get a deeper look at assign and capture in the context of building complex, reusable content β including patterns for assembling dynamic messages and the variable scoping rules you need to know.
π Official documentation:
- Tags/Blocks: https://school.cast.app/liquid/liquid-blocks.html