# Loops (!for)
The !for tag performs configuration unrolling and iteration during the preprocessing phase.
Note: Iterations are evaluated once when the YAML file is loaded. These are not runtime rules. They do not react to live state changes in openHAB.
# When to Use !for
Use the !for tag to dynamically generate repetitive openHAB configuration structures based on the Resolution Context (variables defined in the file, injected via !include or !insert, or environment globals).
Typical use cases in openHAB:
- Generate multiple Things from a list of device IDs.
- Generate multiple Channels from structured metadata.
- Generate multiple Items from a list, map, or equipment definition.
- Generate semantic model structures (rooms, equipment, points).
- Generate metadata blocks (e.g., multiple
stateDescriptionentries). - Unroll lists into a parent array (e.g., multiple tags, multiple groups).
# Basic Syntax
The !for tag evaluates an expression or collection and iterates over its items.
The expression inside the tag specifies the loop variable(s) and the target collection or range.
# Map Key Expansion
When applied as a map key, the unrolled entries are merged directly into the parent map.
things:
!for "id in ['kitchen', 'garage']":
"mqtt:topic:${id}":
label: "${id | label}"
channels:
temperature:
stateTopic: "home/${id}/temp"
Result:
things:
mqtt:topic:kitchen:
label: "Kitchen"
channels:
temperature:
stateTopic: "home/kitchen/temp"
mqtt:topic:garage:
label: "Garage"
channels:
temperature:
stateTopic: "home/garage/temp"
# Range Generation
Use range(start, end) to generate numeric sequences — useful for numbered devices, channels, or items.
items:
!for "i in range(1, 4)":
"Light_${i}":
type: Switch
channel: "zigbee:device:bridge:light_${i}:state"
Result:
items:
Light_1:
type: Switch
channel: zigbee:device:bridge:light_1:state
Light_2:
type: Switch
channel: zigbee:device:bridge:light_2:state
Light_3:
type: Switch
channel: zigbee:device:bridge:light_3:state
TIP
Ruby-style range syntax is also supported: [1..3] (inclusive) and [1...4] (exclusive).
Example: !for i in [1..3] loops from 1 to 3.
# Iteration Patterns
The !for tag supports several unpacking and indexing patterns.
# 1. Simple List or Array Iteration
Useful for generating multiple Items or Channels from a simple list.
variables:
sensors: ["temp", "humidity"]
items:
!for "sensor in sensors":
"Sensor_${sensor}":
type: Number
channel: "mqtt:topic:env:${sensor}"
# 2. Map / Dictionary Key-Value Iteration
Ideal for equipment definitions where each entry has structured metadata.
variables:
equipment_channels:
ch1: { label: "Main Light", type: "Switch" }
ch2: { label: "Fan", type: "Switch" }
channels:
!for "id, cfg in equipment_channels":
"${id}":
label: "${cfg.label}"
type: "${cfg.type}"
# 3. Tuple and List Unpacking
Useful for defining multiple Things or Items with paired attributes.
things:
!for "id, binding in [['Light1', 'mqtt'], ['Temp1', 'zigbee']]":
"${binding}:generic:${id}":
label: "${id}"
# 4. Index & Item Enumeration (enumerate)
Use the enumerate filter or function when you need both the zero-based index and the collection item simultaneously during iteration.
When applied to maps, it pairs the index with map entry objects, allowing access via .key and .value.
variables:
items: [alpha, beta, gamma]
mapping:
kitchen: Light
garage: Door
devices:
# Using the enumerate filter with list unpacking
!for "index, item in items | enumerate":
"dev_${index}": "${item}"
# Using the enumerate function with map entry unpacking
!for "idx, entry in enumerate(mapping)":
"${entry.key}_${idx}": "${entry.value}"
# Conditional Filtering
Filter loop items using an inline if clause.
variables:
sensors:
- { id: "s1", enabled: true }
- { id: "s2", enabled: false }
- { id: "s3", enabled: true }
items:
!for "sensor in sensors if sensor.enabled":
"Sensor_${sensor.id}":
type: Number
Result:
items:
Sensor_s1:
type: Number
Sensor_s3:
type: Number
# Disambiguating Duplicate Keys
YAML requires each key in a map to be unique.
A !for loop is treated as a map key, and its entire scalar value — including any inline comments — is used for key comparison.
A duplicate‑key error only occurs when two !for blocks have identical key scalars.
If the expressions differ, the keys are already unique and no special handling is required.
items:
!for "id, label in equipment":
"Item_${id}_A": "${label}"
!for "id, label in equipment": # Error! Identical key → duplicate
"Item_${id}_B": "${label}"
To make identical expressions unique, add a harmless inline comment:
items:
!for "id, label in equipment # pass1":
"Item_${id}_A": "${label}"
!for "id, label in equipment # pass2":
"Item_${id}_B": "${label}"
Only loops with identical expressions require disambiguation. Different expressions naturally produce different keys.
# Advanced Integration
# Nesting and Composition
Loops are fully recursive. Useful for generating multi-dimensional structures like multi-room equipment, grids, or channel groups.
items:
!for "room in ['Kitchen', 'Garage']":
!for "i in range(1, 3)":
"${room}_Sensor_${i}":
type: Number
Result:
items:
Kitchen_Sensor_1:
type: Number
Kitchen_Sensor_2:
type: Number
Garage_Sensor_1:
type: Number
Garage_Sensor_2:
type: Number
# Common Pitfalls
- Duplicate Keys: Multiple
!forblocks in the same map require unique keys (use inline comments). - Invalid Target: Ensure the loop expression resolves to a valid list or map.
- Static Preprocessing: Loops run at load time only; they cannot react to runtime state changes.
See Expression Syntax for more details.
← Conditionals Include →