Terraform Error Guide: 'Invalid for_each argument' (values cannot be determined) on plan
Fix Terraform's 'Invalid for_each argument' error: handle computed keys, unknown values until apply, null/sensitive maps, and use -target or static keys to break it.
- #terraform
- #troubleshooting
- #errors
- #for_each
Overview
Invalid for_each argument fires when Terraform cannot determine the keys of a for_each map/set at plan time. Terraform must know every instance key before it plans, so it can show you exactly what will be created. If the keys depend on a value that is only known after another resource is applied (an ARN, a generated ID, a list whose length is computed), Terraform cannot expand the for_each and stops.
You will see this on terraform plan:
Error: Invalid for_each argument
on iam.tf line 12, in resource "aws_iam_user_policy_attachment" "attach":
12: for_each = toset([for u in aws_iam_user.team : u.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.
The message is precise: the keys (not the values) are unknown until apply. The fix is to make the keys static/known at plan time, or to apply the resource that produces them first.
Symptoms
planfails withInvalid for_each argument.- The message says keys are “derived from resource attributes that cannot be determined until apply”.
- A
for_eachiterates over outputs of a not-yet-created resource (IDs, ARNs, names). - It appears after switching
counttofor_each, or after adding a new resource into afor_eachexpression.
terraform plan
Error: Invalid for_each argument
The "for_each" map includes keys derived from resource attributes that cannot
be determined until apply.
Common Root Causes
1. Keys derived from a not-yet-created resource
The for_each set/map keys come from an attribute (ID/ARN/name) of a resource Terraform has not applied yet.
grep -rn 'for_each' iam.tf
resource "aws_iam_user" "team" { for_each = toset(var.usernames) }
resource "aws_iam_user_policy_attachment" "attach" {
for_each = toset([for u in aws_iam_user.team : u.name]) # unknown keys
}
u.name is computed during apply, so the keys are unknown at plan.
2. A computed list/length feeding for_each
The collection length depends on a resource attribute, so Terraform cannot know how many keys exist.
grep -rn 'for_each\|length(' main.tf
for_each = { for az in data.aws_availability_zones.live.names : az => az }
If data.aws_availability_zones.live is itself gated by a computed value, the names are unknown.
3. Keys built from another resource’s id/arn
Using IDs as map keys guarantees unknown keys, because IDs are assigned by the provider during apply.
grep -rn 'for_each' subnets.tf
for_each = { for s in aws_subnet.app : s.id => s.cidr_block } # id is computed
Key off a static input (the subnet name/index) instead of the computed id.
4. A whole map value is unknown (not just values)
Even if you intend only the values to be computed, putting a computed expression where Terraform must hash keys makes the keys unknown.
grep -rn 'for_each' secrets.tf
for_each = aws_secretsmanager_secret.app # the map itself isn't known if app is computed-keyed
5. Null or sensitive value in the for_each
A null in the set, or a sensitive value used as a key, prevents Terraform from determining the key set.
terraform console <<<'toset([for u in var.users : u.email])'
Error: Invalid function argument
toset: a null value cannot be used as a set element
6. switched from count to for_each over computed data
Refactoring count (index-based) to for_each (key-based) over the same computed list surfaces the key problem that count masked.
git diff main.tf | grep -E 'count|for_each'
- count = length(aws_instance.node)
+ for_each = { for i in aws_instance.node : i.id => i }
Diagnostic Workflow
Step 1: Identify which expression feeds for_each
terraform plan 2>&1 | grep -A4 "Invalid for_each"
The file/line points at the exact for_each whose keys are unknown.
Step 2: Check whether the keys are computed
grep -rn 'for_each' <FILE>
Trace the keys back: do they come from var/local (known) or from a resource/data id/arn/name (computed)?
Step 3: Test the key set in the console
terraform console
> keys({ for u in aws_iam_user.team : u.name => u })
(known after apply)
known after apply for the keys confirms the cause.
Step 4: Re-key on a known input
grep -rn 'for_each' <FILE>
Switch the key to a static input you already have at plan time:
# instead of keying on the computed u.name, key on the input map
resource "aws_iam_user" "team" {
for_each = toset(var.usernames)
}
resource "aws_iam_user_policy_attachment" "attach" {
for_each = aws_iam_user.team # known keys = var.usernames
user = each.value.name # computed value is fine here
}
Step 5: If keys genuinely must be computed, apply in stages
terraform apply -target=aws_iam_user.team
terraform apply
Apply the resource that produces the keys first; on the second run the keys are known.
Example Root Cause Analysis
A plan fails after wiring policy attachments to a set of users:
Error: Invalid for_each argument
on iam.tf line 18, in resource "aws_iam_user_policy_attachment" "attach":
18: for_each = toset([for u in aws_iam_user.team : u.name])
The "for_each" set includes values derived from resource attributes that cannot
be determined until apply.
The for_each keys are built from aws_iam_user.team[*].name, which is computed during apply. But the user names are also exactly what is in var.usernames at plan time — the code is reaching for the computed attribute unnecessarily.
grep -rn 'for_each' iam.tf
resource "aws_iam_user" "team" {
for_each = toset(var.usernames)
}
Because aws_iam_user.team is itself keyed by the known var.usernames, the attachment can iterate the resource map directly — those keys are known at plan time.
Fix: iterate the resource map, not a list of computed names.
resource "aws_iam_user_policy_attachment" "attach" {
for_each = aws_iam_user.team # keys = var.usernames (known)
user = each.value.name
policy_arn = var.policy_arn
}
terraform plan now expands the for_each and shows one attachment per user.
Prevention Best Practices
- Key
for_eachon values you control at plan time (input variables, locals, static names) — never on a resource’s computedid/arn. - When iterating over a resource created with
for_each, iterate the resource map itself (for_each = aws_x.y) so the keys carry over as known. - Strip
nulls and avoid sensitive values in the key set;compact()and explicit defaults help. - If keys truly depend on created infrastructure, split the apply (
-targetthe producer first) or restructure so the dependency is one-directional. - Prefer
for_eachovercountfor stable identity, but expect it to surface key-determinability thatcounthid. - For triage, the free incident assistant can trace the
for_eachexpression to the computed attribute that breaks it. More patterns in the Terraform guides.
Quick Command Reference
# Locate the failing for_each
terraform plan 2>&1 | grep -A4 "Invalid for_each"
# Inspect the expression feeding for_each
grep -rn 'for_each' <FILE>
# Test whether the keys are known
terraform console
> keys(<FOR_EACH_EXPRESSION>)
# Re-key on the resource map (keys carry over as known)
# for_each = aws_iam_user.team
# If keys must be computed, stage the apply
terraform apply -target=<PRODUCER>
terraform apply
Conclusion
Invalid for_each argument means Terraform cannot determine the keys of your for_each at plan time. The usual root causes:
- Keys derived from a not-yet-created resource’s attributes.
- A computed list/length feeding the collection.
- Keying on a computed
id/arninstead of a static input. - The whole map being unknown at plan.
- A
nullor sensitive value in the key set. - A
count→for_eachrefactor over computed data.
Re-key on values known at plan time (variables, the resource map itself), strip nulls, and only fall back to a staged -target apply when the keys genuinely must come from created infrastructure.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.