This Terraform error occurs when you reference an attribute on the 'each' object that doesn't exist. The 'each' object only provides 'key' and 'value' attributes. Fix it by using the correct attribute or checking your for_each input structure.
The "Invalid each attribute" error in Terraform indicates that you're trying to access an attribute on the "each" object that doesn't exist. When using "for_each" to iterate over resources or modules, Terraform provides exactly two attributes on the "each" object: "key" (the current map key) and "value" (the current map value). This error commonly occurs when developers try to access attributes that don't exist on the "each" object itself, such as "id", "name", or other properties. The error can also happen when the data structure passed to "for_each" doesn't match what the code expects, or when trying to access nested properties using dot notation without understanding the data structure. Unlike "count.index" which provides a numeric index, or "count.primary" which provides a boolean, the "each" object has a fixed, simple interface with only "key" and "value" available for all iterations.
The "each" object in Terraform only provides two attributes, regardless of the data being iterated:
Available attributes:
- each.key: The key from the map (string)
- each.value: The value from the map (can be a string, number, object, list, etc.)
Incorrect - trying to access non-existent attributes:
resource "aws_instance" "example" {
for_each = var.instances
ami = each.id # ERROR: 'each' has no 'id' attribute
instance_type = each.name # ERROR: 'each' has no 'name' attribute
}Correct - using only 'key' and 'value':
resource "aws_instance" "example" {
for_each = var.instances
ami = each.value.ami
instance_type = each.value.instance_type
tags = {
Name = each.key # Use the key for naming
}
}Remember: no matter what data structure you pass to "for_each", you always access it through "each.key" and "each.value".
Make sure you understand what data structure you're iterating over:
When iterating a map of objects:
variable "instances" {
type = map(object({
ami = string
instance_type = string
}))
default = {
web = {
ami = "ami-12345"
instance_type = "t2.micro"
}
db = {
ami = "ami-67890"
instance_type = "t2.small"
}
}
}
resource "aws_instance" "example" {
for_each = var.instances
ami = each.value.ami # Access nested properties through 'value'
instance_type = each.value.instance_type
tags = {
Name = each.key # 'key' is "web" or "db"
}
}When iterating a set of strings:
variable "availability_zones" {
type = set(string)
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
resource "aws_subnet" "example" {
for_each = var.availability_zones
availability_zone = each.value # 'value' is the zone string directly
vpc_id = aws_vpc.main.id
tags = {
Name = "subnet-${each.key}" # 'key' is auto-generated index for sets
}
}Print your variables during development to verify structure:
output "debug_instances" {
value = var.instances
}Different data types passed to "for_each" require different access patterns:
For map(object): Use each.key for the map key and each.value to access object properties:
for_each = {
"web-1" = { size = "t2.micro", az = "us-east-1a" }
"web-2" = { size = "t2.micro", az = "us-east-1b" }
}
instance_type = each.value.size # Not each.instance_type or each.size
tags = { Name = each.key } # 'web-1' or 'web-2'For map(string): Use each.value as the string directly:
for_each = {
"web" = "ami-12345"
"db" = "ami-67890"
}
ami = each.value # Not each.ami or each.keyFor list converted to map: Convert list to map first using tomap():
# Instead of for_each = var.my_list (which fails for non-sets/maps)
for_each = { for idx, item in var.my_list : idx => item }
# Access the item through each.value
resource_name = each.value.name # Not each.name or each.idNever use count-style attributes:
# WRONG - count attributes don't work with for_each
each.id # ❌ Doesn't exist
each.index # ❌ Doesn't exist (use count.index with count, not for_each)
each.count # ❌ Doesn't exist
# CORRECT - only for_each attributes
each.key # ✅ The map key
each.value # ✅ The map valueHere are fixes for the most common patterns that trigger this error:
Mistake 1: Using each.id when you mean each.key:
# ❌ Wrong
for_each = toset(["web", "db", "cache"])
resource "aws_security_group" "example" {
name = each.id
}
# ✅ Correct
for_each = toset(["web", "db", "cache"])
resource "aws_security_group" "example" {
name = each.value
}Mistake 2: Accessing nested properties incorrectly:
# ❌ Wrong - trying to access nested property directly
for_each = var.instances
instance_type = each.instance_type
# ✅ Correct - use each.value then the property
for_each = var.instances
instance_type = each.value.instance_typeMistake 3: Mixing up key and value usage:
# ❌ Wrong
for_each = { a = "value1", b = "value2" }
output_name = each.value # When you meant the key
# ✅ Correct
for_each = { a = "value1", b = "value2" }
output_name = each.key # If you want the key (a or b)
output_data = each.value # If you want the value (value1 or value2)Mistake 4: Using list instead of map/set:
# ❌ Wrong - lists are not supported directly by for_each
for_each = ["item1", "item2", "item3"]
# ✅ Correct - convert to set or map
for_each = toset(["item1", "item2", "item3"])
# OR
for_each = { for idx, item in ["item1", "item2", "item3"] : idx => item }When you're unsure about your data structure, add debug outputs to understand what you're iterating over:
variable "my_resources" {
type = any # Temporary - use 'any' to accept any structure
}
output "debug_for_each" {
value = var.my_resources
}
output "debug_for_each_type" {
value = "Type check: ${jsonencode(var.my_resources)}"
}
resource "aws_instance" "example" {
for_each = var.my_resources
# Debug output for each iteration
tags = {
DebugKey = each.key
DebugValue = jsonencode(each.value)
}
}Then run:
terraform planCheck the debug outputs to see exactly what structure you're working with. This helps you understand whether "each.value" is a string, number, object, or something else.
Use Terraform console to test expressions:
terraform console
> var.my_resources
> keys(var.my_resources)
> values(var.my_resources)The same principles apply when using "for_each" with modules and data sources:
With modules:
variable "subnet_config" {
type = map(object({
cidr_block = string
az = string
}))
}
module "subnets" {
for_each = var.subnet_config
source = "./modules/subnet"
name = each.key # Map key
cidr_block = each.value.cidr_block # Nested property
az = each.value.az
}
# Access module outputs
output "subnet_ids" {
value = { for k, v in module.subnets : k => v.subnet_id }
}With data sources:
data "aws_ami" "example" {
for_each = toset(["ubuntu", "amazon-linux", "centos"])
filter {
name = "name"
values = ["${each.value}-ami-*"] # Use each.value directly
}
}
output "ami_ids" {
value = { for k, v in data.aws_ami.example : k => v.id }
}Remember: even with modules and data sources, the "each" object still only has "key" and "value" attributes.
Understanding for_each vs count:
- "for_each" creates resources with map-based keys and provides "each.key"/"each.value"
- "count" creates resources with numeric indices and provides "count.index"
- Do not mix attributes: use "each.*" with for_each and "count.*" with count
for_each vs splat expressions:
When you use "for_each" on a resource, you cannot use splat expressions like "aws_instance.example[*].id". Instead, convert to values:
# Wrong with for_each
output "ids" {
value = aws_instance.example[*].id
}
# Correct with for_each
output "ids" {
value = { for k, v in aws_instance.example : k => v.id }
}Dynamic blocks and for_each:
When using "for_each" within a resource and also using dynamic blocks inside:
resource "aws_lb_listener_rule" "example" {
for_each = var.rules
dynamic "condition" {
for_each = each.value.conditions
content {
path_pattern {
values = [condition.value.path]
}
}
}
}Sensitive data in for_each:
Be aware that "for_each" values appear in state and plan outputs. For sensitive data:
resource "aws_instance" "example" {
for_each = var.instances
ami = each.value.ami
# Mark sensitive data
tags = {
Password = sensitive(each.value.password) # Won't show in output
}
}Changing for_each keys:
Adding or removing keys in "for_each" will destroy and recreate resources. Use stable identifiers in your map keys when the resource needs to persist.
Error: Error rendering template: template not found
How to fix "template not found" error in Terraform
Error: Error generating private key
How to fix 'Error generating private key' in Terraform
Error creating Kubernetes Service: field is immutable
How to fix "field is immutable" errors in Terraform
Error: Error creating local file: open: permission denied
How to fix "Error creating local file: permission denied" in Terraform
Error: line endings have changed from CRLF to LF
Line endings have changed from CRLF to LF in Terraform