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.key provides the map key and each.value provides 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 for expressions in locals or outputs to transform map data into lists, sets, or new maps without creating resources.
  • Use for_each when creating resources or modules, with each map entry generating one instance accessible via each.key and each.value.
  • Reference the canonical documentation in website/docs/language/expressions/for.md and website/docs/language/meta-arguments/for_each.md for implementation details.
  • Leverage built-in keys() and values() functions for simple extraction tasks.
  • Ensure maps used with for_each contain 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →