Skip to main content

Loop

{% for %}Repeats inner blocks for each item in a data source.

The Loop block repeats its inner blocks for each item in a data source. You select a query type and arguments in the block interface, and Unblock renders the inner blocks once per item.

Data sources

FunctionWhat it queries
postsInherit the main WordPress query (archives, search)
get_posts({...})Posts, pages, or any custom post type
get_terms({...})Categories, tags, or custom taxonomies
get_users({...})Site users by role
get_items([...])A static array or dynamic data

Data alias

The Data Alias field in the block interface defines the variable name you use to access the current item inside the loop:

{% for post in get_posts({...}) %}    {# post.title, post.excerpt… #}
{% for term in get_terms({...}) %} {# term.name, term.count… #}
{% for user in get_users({...}) %} {# user.name, user.email… #}
{% for item in get_items([...]) %} {# item.name, item.url… #}

You can name the variable anything — it doesn't have to match the function:

{% for product in get_posts({'post_type': 'product'}) %}
{{ product.title }}
{{ product.meta('price') }}
{% endfor %}

The data alias exposes all fields of the corresponding provider: Post, Term, or User.

Loop variable

The loop variable gives you metadata about the current iteration:

PropertyTypeDescription
loop.indexintCurrent iteration (1-based)
loop.index0intCurrent iteration (0-based)
loop.revindexintIterations from the end (1-based)
loop.revindex0intIterations from the end (0-based)
loop.firstboolTrue if first iteration
loop.lastboolTrue if last iteration
loop.lengthintTotal number of items
{{ loop.index }}. {{ post.title }}
{{ loop.first ? 'first-item' : '' }}
{{ term.name }}{{ loop.last ? '' : ', ' }}

Empty state

When a loop returns no items, its content doesn't render. Use a Condition block after the loop to show a fallback:

{% for event in get_posts({'post_type': 'event'}) %}
<div class="event">{{ event.title }}</div>
{% endfor %}

{% if not events|length %}
<p>No events found.</p>
{% endif %}

Here events is the query name set in the Loop block interface. You can also use {% if events.found_posts == 0 %}.

Nested loops

Loops can be nested. Use different variable names to avoid conflicts:

{% for term in get_terms({'taxonomy': 'category'}) %}
<h2>{{ term.name }}</h2>

{% for post in get_posts({'category_name': term.slug}) %}
<a href="{{ post.link }}">{{ post.title }}</a>
{% endfor %}
{% endfor %}
Common mistake

If you nest two loops with the same variable name (e.g. two for post in ...), the inner loop shadows the outer one — you lose access to the outer item. Always use different variable names for nested loops.

Pagination

The Loop block can assign a query name so you can reference pagination after the loop. If you need pagination or a total count before the loop, use a Variable block instead.

{% set products = get_posts({'post_type': 'product', 'posts_per_page': 12}) %}

<p>{{ products.found_posts }} products</p>

{% for post in products %}
<h2>{{ post.title }}</h2>
{% endfor %}

{{ products.pagination }}

See Pagination for the full reference — query properties, page objects, and custom markup.

Next steps