Initializing Enclave...

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.key or each.value but the enclosing block has no for_each meta-argument, making the each object undefined in that scope.
  • How to fix it: Add for_each to the block that needs iteration, or remove the orphaned each reference and substitute the correct input variable or local value.
  • Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your failing .tf block 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: The each object is scoped to the module call block, not automatically inherited by resources inside the module. Engineers routinely assume each propagates inward — it does not. Resources inside the module must use var.* inputs, not each.*.
  • Dynamic blocks: A dynamic block exposes its own iterator (default name matches the block label, e.g., ingress.value), not each. Mixing these up is the second most common trigger.
  • locals block: each is never valid inside a locals {} block, even if the local is consumed by a for_each resource. 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_each meta-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 iterator in dynamic blocks to avoid shadowing confusion with each:
    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 before for_each produces 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.

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →