Terraform For Expressions
A baker takes a tray of plain dough, shapes each piece, and places the results on a new tray. A for expression performs the same job on data. It loops over a list or a map, transforms every item, and produces a new list or map. For expressions reshape data without extra resources, which keeps configurations short and readable.
The Basic Idea
Diagram: Input Tray to Output Tray
Input list Rule Output list
+----------+ +-----------------+ +-----------+
| "web" | | | | "WEB" |
| "api" | ----> | upper(item) | ----> | "API" |
| "db" | | | | "DB" |
+----------+ +-----------------+ +-----------+Building a List
Square brackets produce a list. The pattern reads: for each item in the source, produce this value.
variable "names" {
default = ["web", "api", "db"]
}
locals {
upper_names = [for n in var.names : upper(n)]
}The result is ["WEB", "API", "DB"].
Using the Index
Add a second name before the item to receive its position.
locals {
numbered = [for i, n in var.names : "${i}-${n}"]
}The result is ["0-web", "1-api", "2-db"].
Building a Map
Curly braces produce a map. The arrow => separates each key from its value.
locals {
name_lengths = { for n in var.names : n => length(n) }
}The result is { web = 3, api = 3, db = 2 }.
Looping Over an Existing Map
Receive the key and value together.
variable "servers" {
default = {
web = "t3.micro"
api = "t3.small"
}
}
locals {
labels = { for name, size in var.servers : name => "${name}-${size}" }
}The result is { web = "web-t3.micro", api = "api-t3.small" }.
Filtering with if
Add an if clause at the end to keep only the items that pass a test.
variable "users" {
default = [
{ name = "alice", active = true },
{ name = "bob", active = false },
{ name = "carol", active = true }
]
}
locals {
active_users = [for u in var.users : u.name if u.active]
}The result is ["alice", "carol"].
Diagram: Filter in Action
alice (active) ---- passes ----> alice
bob (inactive) ---- blocked --X
carol (active) ---- passes ----> carolSplat Expressions: A Shortcut
When you only need one attribute from every item, the splat symbol [*] saves typing.
aws_instance.web[*].id
# Same result with a for expression
[for inst in aws_instance.web : inst.id]Splat handles simple cases. A for expression handles filtering and renaming.
Grouping Results
Add three dots after a map value to group items that share a key.
locals {
fruits = [
{ name = "apple", color = "red" },
{ name = "cherry", color = "red" },
{ name = "banana", color = "yellow" }
]
by_color = { for f in local.fruits : f.color => f.name... }
}The result is { red = ["apple", "cherry"], yellow = ["banana"] }.
A Practical Example: Tags for Many Buckets
variable "buckets" {
default = ["logs", "images", "backups"]
}
resource "aws_s3_bucket" "this" {
for_each = toset(var.buckets)
bucket = "shop-${each.key}-2026"
}
output "bucket_arns" {
value = { for name, b in aws_s3_bucket.this : name => b.arn }
}The output shows each bucket name beside its ARN. The for expression builds the map from the created resources.
Square Brackets Versus Curly Braces
| Wrapper | Output | Needs Arrow |
|---|---|---|
[ for ... ] | List | No |
{ for ... } | Map | Yes, key => value |
Key Points
- A for expression transforms one collection into a new list or map.
- Square brackets build lists, and curly braces build maps with
=>. - Add an
ifclause at the end to filter items. - Use two loop variables to read an index or a key and value together.
- The splat symbol
[*]collects one attribute from every item. - Three dots after a map value group items under a shared key.
