How to fix Error: Invalid for_each value - for_each value must be a set in Terraform
Terraform's for_each meta-argument requires a set or map, not a list. Learn how to convert lists to sets and fix this common configuration error.
Quick fix
Fixes Error: Invalid for_each value - for_each value must be a set
variable "instance_names" {
type = list(string)
default = ["web-server", "app-server", "db-server"]
}
resource "aws_instance" "example" {
for_each = toset(var.instance_names)
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = each.value
}
}- Convert lists to sets using toset()
- Use for expressions to convert lists of objects to maps
- Verify your data source outputs a set or map
- Declare variables with correct types from the start
variable "instance_names" {type = list(string)default = ["web-server", "app-server", "db-server"]}resource "aws_instance" "example" {for_each = toset(var.instance_names)ami = "ami-0c55b159cbfafe1f0"instance_type = "t2.micro"tags = {Name = each.value}}Error: Invalid for_each value - for_each value must be a set
What this error means
The for_each meta-argument in Terraform is used to create multiple instances of a resource based on a collection of values. However, for_each has strict type requirements: it only accepts sets (set(string)) or maps. When you attempt to use a list, tuple, or other unsupported data type with for_each, Terraform returns this error because it cannot iterate over the value in the expected way. The error occurs at configuration validation time, before any resources are created. This is a configuration syntax issue, not a runtime problem. The solution involves converting your data to the correct type using Terraform functions like toset() or by restructuring your data using for expressions.
Typical symptoms
- Terraform returns 'Invalid for_each value' error when running plan or apply
- Error message specifically mentions for_each requires a set or map
- Configuration fails validation even though your values look correct
- Error occurs when passing a list variable to for_each
- Using outputs or data source attributes with for_each causes validation failure
Common causes
- Using a list (list(string)) with for_each instead of a set (set(string))
- Attempting to iterate over a list directly without converting it to a set
- Passing a tuple or other unsupported collection type to for_each
- Using dynamic values from data sources or computed attributes that don't result in a set or map
- Attempting to use for_each with a string value instead of a collection
How to fix "Error: Invalid for_each value - for_each value must be a set"
If you have a variable declared as list(string), wrap it with the toset() function when passing it to for_each:
variable "instance_names" {
type = list(string)
default = ["web-server", "app-server", "db-server"]
}
resource "aws_instance" "example" {
for_each = toset(var.instance_names)
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = each.value
}
}The toset() function converts your list into a set, which for_each requires. Each element becomes accessible via each.value.
When working with complex data structures like lists of objects, use a for expression to create a map:
variable "instances" {
type = list(object({
name = string
type = string
}))
default = [
{ name = "web", type = "t2.micro" },
{ name = "app", type = "t2.small" }
]
}
resource "aws_instance" "example" {
for_each = { for item in var.instances : item.name => item }
ami = "ami-0c55b159cbfafe1f0"
instance_type = each.value.type
tags = {
Name = each.key
}
}This creates a map where keys are instance names and values are the full objects. Access properties via each.value.property.
When using data sources or locals to populate for_each, ensure the output is a set or map. If it's a list, apply toset():
data "aws_availability_zones" "available" {
state = "available"
}
# This will fail - aws_availability_zones returns a list
# resource "aws_subnet" "example" {
# for_each = data.aws_availability_zones.available.names # ERROR!
# }
# Correct - wrap with toset()
resource "aws_subnet" "example" {
for_each = toset(data.aws_availability_zones.available.names)
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${index(tolist(each.value), each.key)}.0/24"
availability_zone = each.value
}The best approach is to declare your variables with the correct type annotation:
# Use set(string) for collections of strings
variable "environment_names" {
type = set(string)
default = ["dev", "staging", "prod"]
}
# Use map() for key-value data
variable "resource_tags" {
type = map(string)
default = {
environment = "production"
team = "platform"
}
}
# Now for_each works directly without conversion
resource "aws_instance" "by_env" {
for_each = var.environment_names
# ...
}
resource "aws_s3_bucket_tag" "tags" {
for_each = var.resource_tags
# ...
}If for_each depends on computed values that aren't known until apply time, restructure your configuration. Terraform requires for_each values to be known during the plan phase:
# Problem: Using outputs that depend on created resources
# for_each = aws_instance.created[*].id # ERROR - can't predict count until apply
# Solution: Define keys statically, put apply-time values in map values
resource "aws_instance" "servers" {
for_each = {
"server1" = {}
"server2" = {}
}
# ...
}
resource "aws_eip" "servers" {
for_each = aws_instance.servers
instance = each.value.id
}Keep the iteration keys static and place computed values in the map values instead.
After making your changes, validate your configuration:
terraform validateIf successful, you'll see: "Success! The configuration is valid."
Then run plan to see what Terraform intends to create:
terraform planThe error should now be resolved, and you can proceed with apply.
Advanced notes
When working with sensitive values, you cannot use them directly in for_each because Terraform cannot predict the for_each value during planning. To work around this, split sensitive values from keys: declare keys as a static set/map, and reference sensitive values only in the map values.
For resource attributes that can't be determined until apply time, restructure to keep iteration keys static (defined directly in your config) and place the computed values in the map values instead. This allows Terraform to predict the for_each behavior during planning.
On older Terraform versions (pre-0.12), for_each was not available; use count with index() instead, though for_each is preferred in modern configurations.
Frequently asked questions
- What does "Error: Invalid for_each value - for_each value must be a set" mean?
- The for_each meta-argument in Terraform is used to create multiple instances of a resource based on a collection of values. However, for_each has strict type requirements: it only accepts sets (set(string)) or maps. When you attempt to use a list, tuple, or other unsupported data type with for_each, Terraform returns this error because it cannot iterate over the value in the expected way.
- How do I fix "Error: Invalid for_each value - for_each value must be a set"?
- Terraform's for_each meta-argument requires a set or map, not a list. Learn how to convert lists to sets and fix this common configuration error.
- What causes "Error: Invalid for_each value - for_each value must be a set"?
- Using a list (list(string)) with for_each instead of a set (set(string)) Attempting to iterate over a list directly without converting it to a set Passing a tuple or other unsupported collection type to for_each
- Is the "Error: Invalid for_each value - for_each value must be a set" error serious?
- It is a medium-severity error โ more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- ๐Terraform Official Documentation
- ๐Terraform Official Docs: for_each(Official Docs)
- ๐HashiCorp: Troubleshoot Terraform(Official Docs)
- ๐