Loops

A loop repeats a chunk of template for every item in a collection. Use {% for item in items %} in regular paragraphs and {% tr for item in items %} when the loop spans Word table rows.

How to write loops

Plain for loop

Basic for / endfor

{% for product in products %}

{{ product.name }} — {{ product.balance }}

{% endfor %}

Empty-loop else

Renders the {% else %} branch when the collection is empty.

for / else / endfor

{% for product in products %}

{{ product.name }}

{% else %}

No products to display.

{% endfor %}

Nested loops

Loop inside a loop

{% for category in categories %}

Category: {{ category.name }}

{% for asset in category.assets %}

- {{ asset.name }}: {{ asset.value }}

{% endfor %}

{% endfor %}

Loop variables

Inside a loop you can reference the standard Jinja2 loop object:

  • loop.index — current iteration, 1-indexed.
  • loop.index0 — current iteration, 0-indexed.
  • loop.firsttrue on the first iteration.
  • loop.lasttrue on the last iteration.
  • loop.length — total number of items.
  • loop.revindex — iterations remaining (1-indexed from the end).
Numbered list with loop.index

{% for product in products %}

{{ loop.index }}. {{ product.name }}

{% endfor %}

How loops affect field detection

When AdviceDocs parses a loop:

  • The collection (products) is recorded as a parent field with is_array = true.
  • Child references like product.name are recorded under the parent collection (so the editor shows them nested beneath products).
  • The loop variable name (product) is bound to the collection at extraction time — it doesn’t exist as a standalone field.

Loop variable typo warning

If a field inside the loop body uses a name that looks like the loop variable but isn’t (e.g. {{ rec_product.name }} in a loop declared as {% for product in products %}), AdviceDocs still records the field but flags it with a warning along the lines of “Did you mean ‘product’ instead of ‘rec_product’?”

Configuration after upload

The collection field (e.g. products) appears in the field tree with its is_array flag set. You can give it a description and pick a type for the children, just like any other field. There are no loop-specific settings — the loop is purely a template-time construct.

Common pitfalls

Use {% tr for %} inside Word tables

A plain {% for %} placed in the first cell of a row breaks the table layout because Word XML doesn’t cleanly preserve open-block-then-row-then-close nesting. Use {% tr for item in items %} in the first cell of the looped row and {% endtr %} in the last. See the Tables page for examples.

Missing endfor

Every {% for %} must close with {% endfor %}; every {% tr for %} closes with {% endtr %}. Unbalanced loops raise a Jinja2 syntax error at upload time and the field list won’t generate.

Loops can sit inside if blocks

It’s fine to wrap a loop in a conditional: {% if has_products %}{% for p in products %}...{% endfor %}{% endif %}. Each child field inside picks up has_products as a condition dependency.

Syntax to copy

Plain loop

{% for product in products %}

{{ product.name }}: {{ product.balance }}

{% endfor %}

Loop with index and first/last

{% for product in products %}

{% if loop.first %}Recommended portfolio:

{% endif %}

{{ loop.index }}. {{ product.name }}{% if not loop.last %},{% endif %}

{% endfor %}

Empty-loop fallback

{% for product in products %}

{{ product.name }}

{% else %}

No products recommended.

{% endfor %}

Table-row loop
Name
Balance
{% tr for product in products %}
{{ product.name }}
{{ product.balance }}
{% endtr %}