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 configure it from the block's sidebar panel — pick a query type, enter arguments, and set a variable name.

Query types
Select a query type from the dropdown in the sidebar:
| Query type | What it queries | Arguments | Reference |
|---|---|---|---|
| Post | Posts, pages, CPTs | PHP array | Query Posts |
| Term | Categories, tags, taxonomies | PHP array | Query Terms |
| User | Users by role | PHP array | Query Users |
| Item | Static data, repeaters | JSON array | Query Items |
Enable Inherit Query instead to use the main WordPress query — for archive pages, search results, and custom post type archives.
Any argument value can be a dynamic expression instead of a static value — for example post.id or post.categories[0].slug.
Data alias
The Data Alias field sets the variable name for the current item inside the loop. If you're querying products, set it to product — you'll then access product.title, product.price, etc.
You can name it anything. The alias determines which provider fields are available:
- Post query → Post provider (or Product for WooCommerce)
- Term query → Term provider
- User query → User provider
Query name
The Query Name field assigns a name to the query results so you can reference them outside the loop — for pagination, total counts, or empty state checks.
If you set the query name to products, you can use products.found_posts and products.pagination after the loop. See Pagination for details.
Template syntax
In the HTML inspector, the Loop block displays as a {% for %} tag. The sidebar settings map directly:
| Sidebar field | Inspector syntax |
|---|---|
Data alias: product, Query type: Post | {% for product in get_posts() %} |
Data alias: term, Query type: Term | {% for term in get_terms() %} |
| Inherit Query enabled | {% for post in posts %} |
You can edit the template directly in the inspector. The query arguments appear inside the function call using Twig hash syntax:
{% for product in get_posts({'post_type': 'product', 'posts_per_page': 12}) %}
<h2>{{ product.title }}</h2>
<span>{{ product.price|currency }}</span>
{% endfor %}
Loop variable
Inside a loop, the loop variable gives you metadata about the current iteration:
| Property | Type | Description |
|---|---|---|
loop.index | int | Current iteration (1-based) |
loop.index0 | int | Current iteration (0-based) |
loop.first | bool | True if first iteration |
loop.last | bool | True if last iteration |
loop.length | int | Total 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.
Set a query name on the Loop block (e.g. events), then check the result:
{% if events.found_posts == 0 %}
<p>No events found.</p>
{% endif %}
Nested loops
Loops can be nested — for example, list categories with their posts. Use different data aliases 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 %}
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.
Next steps
- Pagination — prev/next links and page numbers
- Query Posts — posts, pages, and custom post types
- Query Terms — categories, tags, and taxonomies
- Query Users — users by role
- Query Items — static data and repeaters
- Variable block — store queries for reuse