Skip to content

Loops Guide

Loops let you repeat content for each item in a list. They're essential for creating dynamic documents with variable numbers of items like invoices, reports, and listings.

Basic Loop Syntax

Implicit Syntax (Simple)

{{#foreach ArrayName}}
  Content to repeat for each item
{{/foreach}}

The content between {{#foreach}} and {{/foreach}} will be repeated once for each item in the array.

Named Iteration Variable Syntax (Explicit)

{{#foreach item in ArrayName}}
  {{item.Property}}
{{/foreach}}

This syntax gives the current item an explicit name (item), which you can use to access its properties. This is especially useful in nested loops where you need to access parent loop variables. The name in and names starting with @ cannot be used as iteration variables.

Where to Put the Markers

A loop repeats whole paragraphs (or whole table rows):

  • Put {{#foreach ...}} and {{/foreach}} in their own paragraphs, above and below the content to repeat.
  • The marker paragraphs are removed from the output, including any other text in them: in Items: {{#foreach Items}}, the text Items: is lost.
  • A loop whose start and end marker are in the same paragraph ({{#foreach Tags}}{{.}}, {{/foreach}}) produces no output. To show a list on a single line, provide the joined text in your data (for example "TagList": "red, green, blue").
  • Marker keywords are case-insensitive ({{#FOREACH Items}} works); collection names are not.

Simple Lists

Looping Through Text Items

JSON:

{
  "Fruits": ["Apple", "Banana", "Cherry", "Date"]
}

Template:

Available Fruits:

{{#foreach Fruits}}
- {{.}}
{{/foreach}}

Output:

Available Fruits:

- Apple
- Banana
- Cherry
- Date

Note: {{.}} (dot) refers to the current item itself when looping through simple values.

Numbered Lists

JSON:

{
  "Steps": [
    "Preheat oven to 350°F",
    "Mix dry ingredients",
    "Add wet ingredients",
    "Bake for 30 minutes"
  ]
}

Template:

Instructions:

{{#foreach Steps}}
{{@number}}. {{.}}
{{/foreach}}

Output:

Instructions:

1. Preheat oven to 350°F
2. Mix dry ingredients
3. Add wet ingredients
4. Bake for 30 minutes

Note: {{@number}} starts at 1 (since 1.8.0); {{@index}} starts at 0. See Loop Variables below. Word's own automatic numbering (a numbered list style on the repeated paragraph) also works.

Looping Through Objects

Basic Object Lists

JSON:

{
  "Products": [
    {
      "Name": "Widget",
      "Price": 10.00,
      "SKU": "WDG-001"
    },
    {
      "Name": "Gadget",
      "Price": 25.00,
      "SKU": "GDG-002"
    },
    {
      "Name": "Doohickey",
      "Price": 15.00,
      "SKU": "DHK-003"
    }
  ]
}

Template:

Product Catalog:

{{#foreach Products}}
Name: {{Name}}
Price: ${{Price}}
SKU: {{SKU}}
---
{{/foreach}}

Output:

Product Catalog:

Name: Widget
Price: $10.00
SKU: WDG-001
---
Name: Gadget
Price: $25.00
SKU: GDG-002
---
Name: Doohickey
Price: $15.00
SKU: DHK-003
---

Complex Objects

JSON:

{
  "Employees": [
    {
      "Name": "Alice Johnson",
      "Title": "Senior Developer",
      "Email": "alice@company.com",
      "Phone": "+1-555-0100"
    },
    {
      "Name": "Bob Smith",
      "Title": "Product Manager",
      "Email": "bob@company.com",
      "Phone": "+1-555-0101"
    }
  ]
}

Template:

EMPLOYEE DIRECTORY

{{#foreach Employees}}
{{Name}} - {{Title}}
  Email: {{Email}}
  Phone: {{Phone}}

{{/foreach}}

Table Loops

One of the most powerful features is repeating table rows:

Simple Table

Template (create a table in Word):

Product Price Stock
{{#foreach Items}}
{{Name}} ${{Price}} {{Stock}}
{{/foreach}}

Put {{#foreach}} and {{/foreach}} in their own rows. The rows between them are repeated for each item and the marker rows are removed. Putting {{#foreach}} in the first cell and {{/foreach}} in the last cell of the same row is not supported (processing fails). A loop whose start and end markers are both inside one cell (in separate paragraphs) repeats paragraphs inside that cell instead.

If a table loses all its rows (for example, an empty collection and no header row), the whole table is removed. Conditional rows ({{#if}} marker rows, see Conditional Table Rows) also work inside a row loop and are evaluated per item.

JSON:

{
  "Items": [
    { "Name": "Widget", "Price": 10.00, "Stock": 50 },
    { "Name": "Gadget", "Price": 25.00, "Stock": 30 },
    { "Name": "Doohickey", "Price": 15.00, "Stock": 0 }
  ]
}

Result: The table row will be repeated for each item, creating a table with 4 rows total (1 header + 3 data rows).

Invoice Line Items

Template:

Item Quantity Unit Price Total
{{#foreach LineItems}}
{{Description}} {{Quantity}} ${{UnitPrice}} ${{Total}}
{{/foreach}}

JSON:

{
  "LineItems": [
    {
      "Description": "Professional Services",
      "Quantity": 10,
      "UnitPrice": 150.00,
      "Total": 1500.00
    },
    {
      "Description": "Software License",
      "Quantity": 1,
      "UnitPrice": 500.00,
      "Total": 500.00
    },
    {
      "Description": "Support Package",
      "Quantity": 12,
      "UnitPrice": 50.00,
      "Total": 600.00
    }
  ]
}

Loop Variables

Special variables are available inside loops. They only work inside a loop (outside a loop, {{@count}} is left unchanged and reported as missing), and in nested loops they always refer to the innermost loop:

{{@index}} - Current Index

Zero-based index of the current item:

Template:

{{#foreach Items}}
Item #{{@index}}: {{Name}}
{{/foreach}}

Output:

Item #0: Widget
Item #1: Gadget
Item #2: Doohickey

For 1-based numbering, use {{@number}}.

{{@number}} - Current Number (1-based)

One-based position of the current item (@index + 1), for numbered lists (since 1.8.0):

Template:

{{#foreach Items}}
{{@number}}. {{Name}}
{{/foreach}}

Output:

1. Widget
2. Gadget
3. Doohickey

@number also works in conditions, e.g. {{#if @number > 1}}...{{/if}}. In nested loops it refers to the innermost loop, like @index.

{{@first}} - First Item

True for the first iteration only:

JSON:

{
  "Chapters": [
    { "Title": "Introduction", "Pages": 10 },
    { "Title": "Getting Started", "Pages": 25 },
    { "Title": "Advanced Topics", "Pages": 40 }
  ]
}

Template:

{{#foreach Chapters}}
{{#if @first}}
=== FIRST CHAPTER ===
{{/if}}
Chapter: {{Title}} ({{Pages}} pages)
{{/foreach}}

Output:

=== FIRST CHAPTER ===
Chapter: Introduction (10 pages)
Chapter: Getting Started (25 pages)
Chapter: Advanced Topics (40 pages)

{{@last}} - Last Item

True for the last iteration only:

Template:

{{#foreach Tags}}
{{.}}{{#if not @last}},{{/if}}
{{/foreach}}

JSON:

{
  "Tags": ["JavaScript", "Python", "Java", "C#"]
}

Output (one paragraph per item):

JavaScript,
Python,
Java,
C#

(Notice no comma after the last item!)

{{@count}} - Total Count

Total number of items in the loop:

Template:

{{#foreach Orders}}
Order {{@number}} of {{@count}}: {{OrderNumber}}
{{/foreach}}

JSON:

{
  "Orders": [
    { "OrderNumber": "ORD-001" },
    { "OrderNumber": "ORD-002" },
    { "OrderNumber": "ORD-003" }
  ]
}

Output:

Order 1 of 3: ORD-001
Order 2 of 3: ORD-002
Order 3 of 3: ORD-003

{{@count}} is not available outside the loop. To show the total before the list, add it to your data (for example "OrderCount": 3).

Nested Loops

You can nest loops within each other for complex data structures:

Two Levels

JSON:

{
  "Departments": [
    {
      "Name": "Engineering",
      "Employees": [
        { "Name": "Alice", "Role": "Senior Dev" },
        { "Name": "Bob", "Role": "Junior Dev" }
      ]
    },
    {
      "Name": "Sales",
      "Employees": [
        { "Name": "Charlie", "Role": "Account Manager" },
        { "Name": "Diana", "Role": "Sales Rep" }
      ]
    }
  ]
}

Template:

{{#foreach Departments}}
Department: {{Name}}
{{#foreach Employees}}
  - {{Name}} ({{Role}})
{{/foreach}}

{{/foreach}}

Output:

Department: Engineering
  - Alice (Senior Dev)
  - Bob (Junior Dev)

Department: Sales
  - Charlie (Account Manager)
  - Diana (Sales Rep)

Three Levels

JSON:

{
  "Regions": [
    {
      "Name": "North America",
      "Countries": [
        {
          "Name": "USA",
          "Cities": ["New York", "Los Angeles", "Chicago"]
        },
        {
          "Name": "Canada",
          "Cities": ["Toronto", "Vancouver", "Montreal"]
        }
      ]
    },
    {
      "Name": "Europe",
      "Countries": [
        {
          "Name": "UK",
          "Cities": ["London", "Manchester"]
        }
      ]
    }
  ]
}

Template:

{{#foreach Regions}}
Region: {{Name}}
{{#foreach Countries}}
  Country: {{Name}}
{{#foreach Cities}}
    - {{.}}
{{/foreach}}
{{/foreach}}

{{/foreach}}

Conditionals in Loops

Combine loops with conditionals for powerful templates:

Conditional Content

JSON:

{
  "Products": [
    { "Name": "Widget", "Price": 10, "InStock": true },
    { "Name": "Gadget", "Price": 25, "InStock": false },
    { "Name": "Doohickey", "Price": 15, "InStock": true }
  ]
}

Template:

Product List:

{{#foreach Products}}
{{Name}} - ${{Price}}
{{#if InStock}}
  ✓ Available now
{{#else}}
  ❌ Out of stock
{{/if}}

{{/foreach}}

Filtering with Conditionals

JSON:

{
  "Orders": [
    { "Id": "001", "Status": "Completed", "Total": 100 },
    { "Id": "002", "Status": "Pending", "Total": 50 },
    { "Id": "003", "Status": "Completed", "Total": 200 },
    { "Id": "004", "Status": "Cancelled", "Total": 75 }
  ]
}

Template:

Completed Orders:

{{#foreach Orders}}
{{#if Status = "Completed"}}
Order {{Id}}: ${{Total}}
{{/if}}
{{/foreach}}

Output:

Completed Orders:

Order 001: $100
Order 003: $200

Variable Resolution in Nested Loops

When you use a variable inside a loop, Templify searches for it in a specific order. Understanding this resolution order is important for working with nested loops.

Resolution Order

Variables are resolved from innermost scope to outermost scope (first match wins):

1. Current loop item (innermost)
2. Parent loop item
3. Grandparent loop item
4. ... (any additional parent loops)
5. Global context (outermost)

Basic Example

JSON:

{
  "CompanyName": "Acme Corp",
  "Departments": [
    {
      "DepartmentName": "Engineering",
      "Employees": [
        { "EmployeeName": "Alice" },
        { "EmployeeName": "Bob" }
      ]
    }
  ]
}

Template:

{{#foreach Departments}}
{{CompanyName}} - {{DepartmentName}}
{{#foreach Employees}}
  Employee: {{EmployeeName}}
  Department: {{DepartmentName}}
  Company: {{CompanyName}}
{{/foreach}}
{{/foreach}}

How resolution works inside the inner loop: - {{EmployeeName}} → Found on current loop item (Employee) - {{DepartmentName}} → Not on Employee → Found on parent loop item (Department) - {{CompanyName}} → Not on Employee → Not on Department → Found in global context

Variable Shadowing

When both inner and outer contexts have a property with the same name, the inner one always wins. This is called "shadowing".

JSON:

{
  "Categories": [
    {
      "Name": "Electronics",
      "Items": [
        { "Name": "Laptop" },
        { "Name": "Mouse" }
      ]
    }
  ]
}

Template:

{{#foreach Categories}}
Category: {{Name}}           ← Shows "Electronics"
{{#foreach Items}}
  Item: {{Name}}             ← Shows "Laptop" / "Mouse" (inner shadows outer)
{{/foreach}}
{{/foreach}}

Output:

Category: Electronics
  Item: Laptop
  Item: Mouse

⚠️ Important: Inside the inner loop, there is no way to access the outer Name using implicit syntax because it's shadowed by the inner Name. However, you can use named iteration variables to solve this problem.

Best Practice: Use Unique Property Names

To avoid shadowing issues, use unique property names at each level:

❌ Problematic (same name at multiple levels):

{
  "Categories": [
    {
      "Name": "Electronics",
      "Items": [
        { "Name": "Laptop" }
      ]
    }
  ]
}

✅ Better (unique names):

{
  "Categories": [
    {
      "CategoryName": "Electronics",
      "Items": [
        { "ItemName": "Laptop" }
      ]
    }
  ]
}

Template:

{{#foreach Categories}}
Category: {{CategoryName}}
{{#foreach Items}}
  Item: {{ItemName}}
  Category: {{CategoryName}}    ← Now accessible!
{{/foreach}}
{{/foreach}}

Named Iteration Variables

An alternative to renaming properties is to use named iteration variables. This syntax lets you give each loop item an explicit name, making it possible to access parent loop variables even when property names conflict.

Syntax:

{{#foreach variableName in CollectionName}}
  {{variableName.Property}}
{{/foreach}}

Example with shadowed names:

JSON:

{
  "Categories": [
    {
      "Name": "Electronics",
      "Items": [
        { "Name": "Laptop" },
        { "Name": "Mouse" }
      ]
    }
  ]
}

Template using named iteration variables:

{{#foreach category in Categories}}
Category: {{category.Name}}
{{#foreach item in category.Items}}
  Item: {{item.Name}}
  Category: {{category.Name}}    ← Parent accessible via named variable!
{{/foreach}}
{{/foreach}}

Output:

Category: Electronics
  Item: Laptop
  Category: Electronics
  Item: Mouse
  Category: Electronics

Key benefits: - Access parent loop variables even when names conflict - More explicit and readable templates - Both {{item.Name}} (explicit) and {{Name}} (implicit) work within the loop

Mixed syntax example:

{{#foreach category in Categories}}
{{category.Name}}:                    ← Named variable
{{#foreach Items}}                    ← Implicit syntax for inner loop
  - {{Name}} ({{category.Name}})      ← Implicit + parent named variable
{{/foreach}}
{{/foreach}}

Accessing Global Variables in Nested Loops

Global variables remain accessible at any nesting depth (unless shadowed):

JSON:

{
  "ShowPrices": true,
  "Currency": "USD",
  "Orders": [
    {
      "OrderId": "ORD-001",
      "Items": [
        { "ProductName": "Widget", "Price": 10 }
      ]
    }
  ]
}

Template:

{{#foreach Orders}}
Order: {{OrderId}}
{{#foreach Items}}
  {{#if ShowPrices}}
    {{ProductName}}: {{Price}} {{Currency}}
  {{#else}}
    {{ProductName}}: Contact for pricing
  {{/if}}
{{/foreach}}
{{/foreach}}

Here, {{ShowPrices}} and {{Currency}} are resolved from the global context because they're not defined on the loop items.

Using Conditionals with Parent Data

You can use conditionals that reference parent loop properties:

JSON:

{
  "Categories": [
    {
      "CategoryName": "Premium",
      "IsPremium": true,
      "Products": [
        { "ProductName": "Gold Widget" },
        { "ProductName": "Gold Gadget" }
      ]
    },
    {
      "CategoryName": "Standard",
      "IsPremium": false,
      "Products": [
        { "ProductName": "Basic Widget" }
      ]
    }
  ]
}

Template:

{{#foreach Categories}}
{{CategoryName}} Products:
{{#foreach Products}}
  {{#if IsPremium}}★ {{/if}}{{ProductName}}
{{/foreach}}

{{/foreach}}

Output:

Premium Products:
  ★ Gold Widget
  ★ Gold Gadget

Standard Products:
  Basic Widget

The {{#if IsPremium}} condition checks the parent category's property from within the inner Products loop.

Summary Table

Scenario Resolution
Property only on current item ✅ Found immediately
Property only on parent item ✅ Found after checking current
Property only in global context ✅ Found after checking all loop levels
Same property name at multiple levels ⚠️ Innermost wins (shadowing)
Need to access shadowed property ✅ Use named iteration variables or unique property names

Empty Arrays

What happens when an array is empty?

JSON:

{
  "Items": []
}

Template:

Items:
{{#foreach Items}}
- {{Name}}
{{/foreach}}
(End of list)

Output:

Items:
(End of list)

The loop body simply doesn't execute when the array is empty.

When the collection is missing from the data or is null, the loop is removed in the same way, and the developer gets a warning (MissingLoopCollection / NullLoopCollection). When the value is not a list at all (for example a text or a number), processing fails with an error such as Variable 'Items' is not a collection.

Handling Empty Arrays

Template:

{{#if Items}}
Items:
{{#foreach Items}}
- {{Name}}
{{/foreach}}
{{#else}}
No items available.
{{/if}}

Null Items and Null Properties

Arrays may contain null entries, and item properties may be null:

JSON:

{
  "Notes": "Global note",
  "Tags": ["a", null, "b"],
  "Items": [{ "Name": "A", "Notes": null }]
}

  • A null entry is iterated like any other item: {{.}} / {{this}} (and {{item}} / {{item.Name}} with a named iteration variable) render empty, {{#if .}} is false, and {{@index}} / {{@count}} count it.
  • A property that exists on the item with a null value renders empty. It does not fall back to a variable of the same name in an outer scope: with the data above, a loop over Items whose content paragraph is {{Name}}:{{Notes}} renders A:, not A:Global note. {{#if Notes exists}} is true for such a property.
  • A null entry has no properties, so implicit names like {{Company}} inside the loop still resolve from outer scopes.

Common Patterns

Comma-Separated List

Loops repeat whole paragraphs, so a list on a single line (Authors: Alice Johnson, Bob Smith, Charlie Brown) cannot be built with {{#foreach}}. Provide the joined text in your data instead:

JSON:

{
  "AuthorList": "Alice Johnson, Bob Smith, Charlie Brown"
}

Template:

Authors: {{AuthorList}}

A one-item-per-paragraph list with separators is shown under {{@last}}.

Bulleted List

Template:

Key Features:
{{#foreach Features}}
• {{.}}
{{/foreach}}

Numbered List (1-based)

Use {{@number}} (since 1.8.0):

JSON:

{
  "Tasks": [
    { "Task": "First task" },
    { "Task": "Second task" },
    { "Task": "Third task" }
  ]
}

Template:

{{#foreach Tasks}}
{{@number}}. {{Task}}
{{/foreach}}

Alternating Rows

JSON:

{
  "Items": [
    { "Name": "Item 1" },
    { "Name": "Item 2" },
    { "Name": "Item 3" },
    { "Name": "Item 4" }
  ]
}

Template (in Word with background color):

{{#foreach Items}}
{{@index}}: {{Name}}
{{/foreach}}

Then manually apply alternating row colors in Word, or use conditional formatting based on {{@index}} if you process the index modulo 2 in your JSON.

Section Separators

Template:

{{#foreach Sections}}
{{Title}}

{{Content}}

{{#if not @last}}
─────────────────
{{/if}}
{{/foreach}}

This adds a separator between sections but not after the last one.

Real-World Examples

Invoice

JSON:

{
  "InvoiceNumber": "INV-2024-001",
  "InvoiceDate": "2024-01-15",
  "Customer": {
    "Name": "Acme Corporation",
    "Address": "123 Business St, City, State 12345"
  },
  "LineItems": [
    {
      "Description": "Website Design",
      "Quantity": 1,
      "Rate": 5000.00,
      "Amount": 5000.00
    },
    {
      "Description": "Hosting (12 months)",
      "Quantity": 12,
      "Rate": 50.00,
      "Amount": 600.00
    },
    {
      "Description": "Domain Registration",
      "Quantity": 1,
      "Rate": 15.00,
      "Amount": 15.00
    }
  ],
  "Subtotal": 5615.00,
  "Tax": 449.20,
  "Total": 6064.20
}

Template:

INVOICE #{{InvoiceNumber}}
Date: {{InvoiceDate}}

BILL TO:
{{Customer.Name}}
{{Customer.Address}}

ITEMS:
(Word table)
| Description         | Qty          | Rate      | Amount      |
| {{#foreach LineItems}} |           |           |             |
| {{Description}}     | {{Quantity}} | ${{Rate}} | ${{Amount}} |
| {{/foreach}}        |              |           |             |

Subtotal: ${{Subtotal}}
Tax: ${{Tax}}
TOTAL: ${{Total}}

Meeting Attendees

JSON:

{
  "MeetingTitle": "Q1 Planning Session",
  "MeetingDate": "2024-01-20",
  "Attendees": [
    {
      "Name": "Alice Johnson",
      "Department": "Engineering",
      "Role": "Required"
    },
    {
      "Name": "Bob Smith",
      "Department": "Product",
      "Role": "Required"
    },
    {
      "Name": "Charlie Brown",
      "Department": "Design",
      "Role": "Optional"
    }
  ]
}

Template:

Meeting: {{MeetingTitle}}
Date: {{MeetingDate}}

ATTENDEES:

Required:
{{#foreach Attendees}}
{{#if Role = "Required"}}
- {{Name}} ({{Department}})
{{/if}}
{{/foreach}}

Optional:
{{#foreach Attendees}}
{{#if Role = "Optional"}}
- {{Name}} ({{Department}})
{{/if}}
{{/foreach}}

Product Catalog with Categories

JSON:

{
  "Categories": [
    {
      "Name": "Electronics",
      "Products": [
        { "Name": "Laptop", "Price": 999 },
        { "Name": "Mouse", "Price": 25 }
      ]
    },
    {
      "Name": "Books",
      "Products": [
        { "Name": "Learn Python", "Price": 40 },
        { "Name": "Web Design", "Price": 35 }
      ]
    }
  ]
}

Template:

PRODUCT CATALOG

{{#foreach Categories}}
━━━━━━━━━━━━━━━━━━━━
{{Name}}
━━━━━━━━━━━━━━━━━━━━

{{#foreach Products}}
• {{Name}} - ${{Price}}
{{/foreach}}

{{/foreach}}

Troubleshooting

Loop Not Repeating

Check: 1. JSON has an array: "Items": [...] not "Items": {...} 2. Array name matches exactly (case-sensitive) 3. Closing tag is present: {{/foreach}} 4. {{#foreach}} and {{/foreach}} are in their own paragraphs (or their own table rows), not in the same paragraph

Wrong Data Appears

Check: 1. Property names inside loop match the object structure 2. Not confusing parent and nested properties

JSON:

{
  "Products": [
    { "ProductName": "Widget" }
  ]
}

Template:

{{#foreach Products}}
{{Name}}         ← Wrong! Should be {{ProductName}}
{{ProductName}}  ← Correct!
{{/foreach}}

Nested Loops Not Working

Make sure closing tags are in the right order:

❌ Wrong:

{{#foreach A}}
  {{#foreach B}}
  {{/foreach}}
{{/foreach}}     ← Closed A, should close B first

✅ Correct:

{{#foreach A}}
  {{#foreach B}}
  {{/foreach}}   ← Close B
{{/foreach}}     ← Close A

Best Practices

  1. Structure JSON to match template needs - Organize data how you'll display it
  2. Use meaningful property names - ProductName not N1
  3. Use unique property names in nested structures - Avoid Name at multiple levels; use CategoryName, ItemName instead (see Variable Shadowing)
  4. Keep nesting reasonable - 2-3 levels maximum for readability
  5. Test with edge cases - Empty arrays, single items, many items
  6. Use loop variables - {{@first}}, {{@last}} for special formatting
  7. Combine with conditionals - Filter or format items based on properties
  8. Add separators carefully - Use {{#if not @last}} to avoid trailing separators

Next Steps