Fix Terraform Error - each.value Does Not Have Attribute
Fix the Terraform each.value unsupported attribute error. Covers for_each map structures, lookup defaults, try functions, and type-safe variable definitions.
Troubleshooting
Fix the Terraform invalid for_each argument error when keys depend on unknown values. Covers static keys, targeting, locals, and for_each best practices.
The for_each set contains values derived from resource attributes that aren't known until apply time. Terraform needs all for_each keys at plan time. Replace computed values with static variables, pre-defined maps, or split into two applies.
When running terraform plan:
Error: Invalid for_each argument
on main.tf line 10, in resource "aws_route53_record" "records":
10: for_each = toset(aws_acm_certificate.main.domain_validation_options[*].resource_record_name)
The "for_each" set includes values derived from resource attributes
that cannot be determined until apply, and so Terraform cannot
determine the full set of keys that will identify the instances of
this resource.Error: Invalid for_each argument
on main.tf line 5, in resource "aws_subnet" "private":
5: for_each = toset(data.aws_availability_zones.available.names)
The "for_each" argument depends on resource attributes that cannot
be determined until apply.Terraform must know every key in for_each during the planning phase to build the resource graph. When keys come from resources that don't exist yet, Terraform can't determine the set.
ACM certificate validation records:
# BAD — domain_validation_options not known until cert is created
resource "aws_route53_record" "validation" {
for_each = {
for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => dvo
}
}Subnets from a new VPC:
# BAD — subnet IDs not known until VPC is created
resource "aws_instance" "workers" {
for_each = toset(aws_subnet.private[*].id)
}Dynamic outputs from modules:
# BAD — module output not known at plan time
resource "aws_security_group_rule" "rules" {
for_each = module.network.security_group_rules
}Define the keys as variables instead of deriving them from resources:
variable "availability_zones" {
type = list(string)
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
resource "aws_subnet" "private" {
for_each = toset(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, index(var.availability_zones, each.value))
availability_zone = each.value
}variable "servers" {
type = map(object({
instance_type = string
subnet_tier = string
}))
default = {
"web-1" = { instance_type = "t3.micro", subnet_tier = "public" }
"web-2" = { instance_type = "t3.micro", subnet_tier = "public" }
"api-1" = { instance_type = "t3.small", subnet_tier = "private" }
"db-1" = { instance_type = "r5.large", subnet_tier = "private" }
}
}
resource "aws_instance" "servers" {
for_each = var.servers # Keys known at plan time
ami = data.aws_ami.ubuntu.id
instance_type = each.value.instance_type
tags = {
Name = each.key
}
}The ACM validation pattern is the most common trigger. Use the correct approach:
resource "aws_acm_certificate" "main" {
domain_name = var.domain_name
subject_alternative_names = var.san_domains
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
# Use { for } expression with the certificate's known structure
resource "aws_route53_record" "validation" {
for_each = {
for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
zone_id = var.zone_id
name = each.value.name
type = each.value.type
ttl = 60
records = [each.value.record]
}
resource "aws_acm_certificate_validation" "main" {
certificate_arn = aws_acm_certificate.main.arn
validation_record_fqdns = [for record in aws_route53_record.validation : record.fqdn]
}If the above still fails (first-time apply), use -target:
terraform apply -target=aws_acm_certificate.main
terraform applyWhen restructuring isn't practical:
# Step 1: Create the resources that produce dynamic values
terraform apply -target=aws_vpc.main -target=aws_subnet.private
# Step 2: Now all values are known
terraform applyPre-compute the map from known inputs:
variable "domain_names" {
type = list(string)
default = ["example.com", "www.example.com", "api.example.com"]
}
locals {
# Map with known keys — domain names are static
domain_map = { for d in var.domain_names : d => d }
}
resource "aws_acm_certificate" "main" {
domain_name = var.domain_names[0]
subject_alternative_names = slice(var.domain_names, 1, length(var.domain_names))
validation_method = "DNS"
}When for_each can't work, count with a known number sometimes can:
variable "subnet_count" {
type = number
default = 3
}
resource "aws_subnet" "private" {
count = var.subnet_count
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
}| Scenario | Use for_each | Use count |
|---|---|---|
| Known set of named resources | ✅ Best fit | ⚠️ Index-based |
| Number of resources from variable | ⚠️ Convert to map | ✅ Simple |
| Resources from computed values | ❌ Won't work at plan | ❌ Won't work at plan |
| Need stable resource addresses | ✅ Key-based | ❌ Index shifts on removal |
for_each reference any resource that's being created in the same apply?-target for the first apply?count with a known number work instead?-target is needed on first run, document it in READMEmap(object(...)) variables are always known at planterraform plan on clean state — catches this error before it hits CITerraform requires all for_each keys at plan time. If keys come from resources being created in the same apply, Terraform can't build the graph. Use static variables, pre-defined maps, or split into two applies. For the common ACM validation pattern, use the { for } expression with domain names as keys.
Fix the Terraform each.value unsupported attribute error. Covers for_each map structures, lookup defaults, try functions, and type-safe variable definitions.
Fix the Terraform invalid count argument error when count depends on resource attributes not known until apply. Covers for_each, data sources, and targeting.
Fix the Terraform 'Backend configuration changed' error. Migrate state between backends (local to S3, S3 to S3), resolve lock conflicts
Fix Terraform provider version conflicts between modules, lock files, and constraint mismatches. Resolve dependency lock errors, upgrade providers safely