How to Fix Terraform 'The each Object is Not Valid in This Context Outside of for_each' Error
Threat/Impact Level: MEDIUM | Exploitability/Downtime Risk: HIGH (blocks all applies) | Time to Fix: 5–15 mins
TL;DR
- What broke: A resource, module, data block, or local expression references
each.keyoreach.valuebut the enclosing block has nofor_eachmeta-argument, making theeachobject undefined in that scope. - How to fix it: Add
for_eachto the block that needs iteration, or remove the orphanedeachreference and substitute the correct input variable or local value. - Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your failing
.tfblock and get corrected HCL without sending secrets anywhere.
The Incident (What Does the Error Mean?)
Raw error output:
Error: The "each" object is not valid in this context
on main.tf line 14, in resource "aws_iam_role_policy_attachment" "this":
14: role = each.value.role_name
A reference to "each.value" has been used in a context in which it
is unavailable, such as when the configuration for a resource that
does not use "for_each" references "each".
Immediate consequence: terraform plan and terraform apply both hard-fail. No infrastructure changes can be executed until this is resolved. This is a compile-time parse error — Terraform's evaluator cannot construct the resource graph at all.
The Attack Vector / Blast Radius
This error is a deployment blocker, not a runtime warning. The blast radius depends on where in your module tree it surfaces:
- Root module: Every environment sharing this root is dead. No plan, no apply, no destroy.
- Child module called with
for_each: Theeachobject is scoped to the module call block, not automatically inherited by resources inside the module. Engineers routinely assumeeachpropagates inward — it does not. Resources inside the module must usevar.*inputs, noteach.*. - Dynamic blocks: A
dynamicblock exposes its own iterator (default name matches the block label, e.g.,ingress.value), noteach. Mixing these up is the second most common trigger. localsblock:eachis never valid inside alocals {}block, even if the local is consumed by afor_eachresource. This catches even senior engineers off-guard.
Pipeline impact: If this lands in a terraform plan step in CI, the entire pipeline fails, potentially blocking a release train across multiple teams if the module is shared.
How to Fix It (The Solution)
Scenario 1 — Missing for_each on the resource block
The most common case: each is referenced but for_each was accidentally omitted or removed during a refactor.
variable "role_attachments" {
type = map(object({
role_name = string
policy_arn = string
}))
}
resource "aws_iam_role_policy_attachment" "this" {
+ for_each = var.role_attachments
+
role = each.value.role_name
policy_arn = each.value.policy_arn
}
Scenario 2 — each used inside a child module's resource (not the module call)
Bad: Engineer passes a map to the module and tries to use each inside the module's internal resource.
# modules/iam_attachment/main.tf
- # WRONG: each is not in scope here; this is inside the module, not the call site
- resource "aws_iam_role_policy_attachment" "this" {
- role = each.value.role_name
- policy_arn = each.value.policy_arn
- }
+ # CORRECT: accept inputs via variables; let the caller use for_each on the module
+ variable "role_name" { type = string }
+ variable "policy_arn" { type = string }
+
+ resource "aws_iam_role_policy_attachment" "this" {
+ role = var.role_name
+ policy_arn = var.policy_arn
+ }
# root/main.tf — the module CALL is where for_each lives
module "iam_attachment" {
source = "./modules/iam_attachment"
+ for_each = var.role_attachments
+
+ role_name = each.value.role_name
+ policy_arn = each.value.policy_arn
}
Scenario 3 — each inside a locals block (invalid scope)
locals {
- # INVALID: each is never available in locals
- attachment_id = "${each.key}-attachment"
+ # Use a for expression instead
+ attachment_ids = { for k, v in var.role_attachments : k => "${k}-attachment" }
}
Enterprise Best Practice
- Encapsulate iteration at the boundary. The
for_eachmeta-argument belongs on the outermost block that needs multiple instances — typically the module call in the root, not inside the module itself. Keep modules stateless with respect to iteration. - Name your iterators explicitly using
iteratorindynamicblocks to avoid shadowing confusion witheach:dynamic "ingress" { for_each = var.ingress_rules iterator = rule content { from_port = rule.value.from_port } } - Validate inputs with
precondition(Terraform ≥ 1.2) to catch empty maps beforefor_eachproduces zero instances silently.
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. terraform validate as a pre-commit gate
This error is caught at validate time, before any API calls. Add it to .pre-commit-config.yaml:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.92.0
hooks:
- id: terraform_validate
2. TFLint with the AWS ruleset
# .tflint.hcl
plugin "terraform" {
enabled = true
preset = "recommended" # catches invalid each references and missing for_each
}
3. Checkov policy — flag modules missing for_each when each is referenced
Checkov's CKV_TF_1 and custom Rego policies via OPA can enforce that any HCL file containing each.key or each.value has a corresponding for_each in the same block scope.
4. CI pipeline enforcement (GitHub Actions example)
- name: Terraform Validate
run: |
terraform init -backend=false
terraform validate
env:
TF_CLI_ARGS: "-no-color"
Fail the PR. Do not merge. This error in a shared module can cascade to every team consuming it.