How to Iterate Through a Terraform Map Variable: Complete Guide to `for` Expressions and `for_each`
Use for expressions to transform map data inside locals or outputs, and use the for_each meta-argument to create multiple resource instances from a map.
Terraform map variables are collections of key-value pairs that require specific iteration patterns to process effectively. In the hashicorp/terraform repository, the language implementation distinguishes between transforming data with expressions and instantiating infrastructure with meta-arguments. This guide covers both approaches using actual source patterns from the Terraform language documentation.
Understanding Terraform Map Variables
A map variable in Terraform is defined as map(<type>) and contains unique keys paired with values of the specified type. Unlike lists, maps provide named access to values, making them ideal for configuration objects like tags, region mappings, or user definitions.
variable "server_ami" {
type = map(string)
default = {
"us-east-1" = "ami-0c55b159cbfafe1f0"
"eu-west-1" = "ami-0a1b2c3d4e5f67890"
}
}
Method 1: Using for Expressions
The for expression is the primary mechanism for iterating through a Terraform map variable to transform data. According to the source documentation in website/docs/language/expressions/for.md, this expression creates new collections without instantiating resources.
Basic Syntax and List Transformation
The standard syntax iterates over key-value pairs and produces a list:
locals {
ami_list = [for region, ami in var.server_ami : ami]
}
This pattern extracts all values from the map into a list. You can also capture keys: [for k, v in var.map : k].
Filtering Maps with Conditional Logic
Add an if clause to filter entries during iteration:
variable "users" {
type = map(object({
enabled = bool
role = string
}))
}
locals {
active_admins = {
for name, info in var.users : name => info.role
if info.enabled && info.role == "admin"
}
}
Transforming to Complex Objects
Create nested objects by returning a map with the => syntax:
locals {
enriched_servers = {
for region, ami in var.server_ami : region => {
ami_id = ami
region_label = upper(region)
timestamp = timestamp()
}
}
}
Method 2: Using for_each Meta-Argument
When you need to create one resource instance per map entry, use the for_each meta-argument. The implementation in website/docs/language/meta-arguments/for_each.md specifies that this creates a distinct resource object for each element.
Resource Creation Pattern
variable "buckets" {
type = map(string) # {"logs" = "us-east-1", "backups" = "eu-central-1"}
}
resource "aws_s3_bucket" "storage" {
for_each = var.buckets
bucket = "${each.key}-data"
region = each.value
tags = {
Environment = "production"
BucketType = each.key
}
}
Key Constraints and Behavior
The for_each meta-argument has specific requirements according to the Terraform source:
- Known values required: The map must be fully known at plan time. Terraform cannot determine resource instance addresses if keys are computed values unknown until apply.
- Resource addressing: Each instance receives an address like
aws_s3_bucket.storage["logs"], allowing individual targeting. - Access syntax: Inside the block,
each.keyprovides the map key andeach.valueprovides the associated value.
Helper Functions: keys() and values()
Terraform provides built-in functions that often eliminate the need for explicit for expressions. These are defined in the core language functions:
locals {
all_regions = keys(var.server_ami) # Returns ["us-east-1", "eu-west-1"]
all_amis = values(var.server_ami) # Returns ["ami-0c55...", "ami-0a1b..."]
}
Use keys() when you need only the identifiers, and values() when processing just the data elements.
Common Patterns and Examples
Conditional Resource Creation
Filter a map before passing to for_each:
resource "aws_instance" "server" {
for_each = {
for name, config in var.instances : name => config
if config.enabled
}
ami = each.value.ami
instance_type = each.value.instance_type
}
Converting Map to List of Objects
Useful when a module requires list input but you have map data:
locals {
user_list = [
for name, role in var.users : {
username = name
role = role
}
]
}
Summary
- Use
forexpressions inlocalsoroutputsto transform map data into lists, sets, or new maps without creating resources. - Use
for_eachwhen creating resources or modules, with each map entry generating one instance accessible viaeach.keyandeach.value. - Reference the canonical documentation in
website/docs/language/expressions/for.mdandwebsite/docs/language/meta-arguments/for_each.mdfor implementation details. - Leverage built-in
keys()andvalues()functions for simple extraction tasks. - Ensure maps used with
for_eachcontain only known values at plan time to avoid evaluation errors.
Frequently Asked Questions
What is the difference between for and for_each in Terraform?
A for expression is a functional construct that transforms data within expressions, producing new collections like lists or maps. It operates purely in memory and never creates infrastructure. The for_each meta-argument is a resource-level configuration that instructs Terraform to create a separate resource instance for each element in a map or set, with each instance maintaining its own state.
Can I use for_each with a map that has computed values?
No. According to the Terraform source implementation in website/docs/language/meta-arguments/for_each.md, the keys of a map used with for_each must be known at plan time. If the map keys are derived from computed attributes (such as resource IDs generated during apply), Terraform cannot determine the resource instance addresses during the planning phase, resulting in an error. Use static keys or data sources that resolve during refresh.
How do I access the key and value inside a for_each resource?
Inside a resource or module block that uses for_each, Terraform provides two special objects: each.key contains the current map key (or set element), and each.value contains the associated map value. For example, if iterating over var.users where keys are usernames and values are role strings, each.key would be "admin" and each.value would be "Administrator" for that specific instance.
When should I use keys() or values() instead of a for expression?
Use the built-in keys() function when you need only the map keys as a list, and values() when you need only the values as a list. These functions are more concise and readable than writing [for k, v in var.map : k] or [for k, v in var.map : v]. However, if you need to filter entries with an if clause, transform values, or construct objects, you must use a full for expression.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →