TerraformPilot

Troubleshooting

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.

LLuca Berton2 min read

Quick Answer

#

You're referencing an attribute on each.value that doesn't exist in your for_each map. Check that every entry in the map has the attribute you're accessing, or use lookup() / try() with a default value for optional attributes.

The Error

#

When running terraform plan, you encounter:

Error: Unsupported attribute
 
  on main.tf line 12, in resource "aws_instance" "servers":
  12:   instance_type = each.value.instance_type
 
This object does not have an attribute named "instance_type".
Error: Unsupported attribute
 
  on main.tf line 8, in resource "aws_security_group_rule" "rules":
  8:   description = each.value.description
 
This value does not have any attributes.

What Causes This Error

#

1. Missing Attribute in Map Entries

#

Some entries in your map are missing the attribute you reference:

variable "servers" {
  default = {
    web = {
      instance_type = "t3.micro"
      ami           = "ami-12345"
    }
    db = {
      instance_type = "r5.large"
      # Missing "ami" — accessing each.value.ami will fail
    }
  }
}

2. for_each Over a Set of Strings

#

When for_each iterates over a set of strings, each.value is a string — not an object:

# each.value is a STRING here, not an object
resource "aws_iam_user" "users" {
  for_each = toset(["alice", "bob", "carol"])
  name     = each.value          # OK — it's a string
  # name   = each.value.name     # ERROR — string has no .name attribute
}

3. Type Mismatch in Variable

#

The variable type doesn't enforce the expected structure:

# BAD — map(any) doesn't enforce object shape
variable "servers" {
  type = map(any)
}
 
# GOOD — enforce the expected attributes
variable "servers" {
  type = map(object({
    instance_type = string
    ami           = string
  }))
}

4. Nested Object Access

#

Trying to access nested attributes that don't exist at the expected depth:

# each.value.tags.Name when tags might not exist
tags = each.value.tags.Name  # ERROR if tags is null

How to Fix It

#

Solution 1: Define a Type-Safe Variable

#

Use an object type constraint to enforce required attributes:

variable "servers" {
  type = map(object({
    instance_type = string
    ami           = string
    subnet_id     = string
  }))
}
 
resource "aws_instance" "servers" {
  for_each      = var.servers
  ami           = each.value.ami           # Guaranteed to exist
  instance_type = each.value.instance_type # Guaranteed to exist
  subnet_id     = each.value.subnet_id     # Guaranteed to exist
 
  tags = {
    Name = each.key
  }
}

Solution 2: Use lookup() for Optional Attributes

#
variable "servers" {
  type = map(map(string))
  default = {
    web = {
      instance_type = "t3.micro"
    }
    db = {
      instance_type = "r5.large"
      volume_size   = "100"
    }
  }
}
 
resource "aws_instance" "servers" {
  for_each      = var.servers
  ami           = var.ami_id
  instance_type = each.value.instance_type
 
  root_block_device {
    # Use lookup with a default for optional attributes
    volume_size = lookup(each.value, "volume_size", "20")
  }
}

Solution 3: Use try() for Optional Nested Attributes

#
resource "aws_instance" "servers" {
  for_each      = var.servers
  ami           = each.value.ami
  instance_type = each.value.instance_type
 
  # try() returns the default if the attribute doesn't exist
  monitoring = try(each.value.monitoring, false)
 
  tags = {
    Name        = each.key
    Environment = try(each.value.environment, "dev")
    CostCenter  = try(each.value.cost_center, "shared")
  }
}

Solution 4: Use optional() in Object Types (Terraform 1.3+)

#
variable "servers" {
  type = map(object({
    instance_type = string
    ami           = string
    monitoring    = optional(bool, false)
    volume_size   = optional(number, 20)
    environment   = optional(string, "dev")
  }))
}
 
# Now all attributes are accessible — optionals use their defaults
resource "aws_instance" "servers" {
  for_each      = var.servers
  ami           = each.value.ami
  instance_type = each.value.instance_type
  monitoring    = each.value.monitoring  # defaults to false
 
  root_block_device {
    volume_size = each.value.volume_size  # defaults to 20
  }
}

Solution 5: Check for_each Type When Using Strings

#
# When iterating over a set of strings
variable "user_names" {
  type    = set(string)
  default = ["alice", "bob"]
}
 
resource "aws_iam_user" "users" {
  for_each = var.user_names
 
  # each.key and each.value are both the string
  name = each.value  # "alice", "bob" — NOT each.value.name
}
 
# When iterating over a map of objects
variable "users" {
  type = map(object({
    email = string
    role  = string
  }))
}
 
resource "aws_iam_user" "users" {
  for_each = var.users
 
  name = each.key           # The map key
  tags = {
    Email = each.value.email  # Object attribute
    Role  = each.value.role   # Object attribute
  }
}

Solution 6: Debug the Data Structure

#

Add an output to inspect what for_each receives:

output "debug_servers" {
  value = var.servers
}
 
# Or inspect a specific entry
output "debug_first" {
  value = var.servers["web"]
}
terraform plan -var-file=dev.tfvars
# Check the output to see the actual structure

Common Mistakes Quick Reference

#
CodeProblemFix
each.value.name on toset(["a","b"])each.value is a stringUse each.value directly
each.value.ami with map(string)Map values are stringsUse each.value as the string
each.value.optional_keyKey might not existlookup(each.value, "key", "default")
each.value.nested.deepNested object might be nulltry(each.value.nested.deep, "default")

Troubleshooting Checklist

#
  1. ✅ What type is for_each? Set of strings, map of strings, or map of objects?
  2. ✅ Does every map entry have the attribute you're accessing?
  3. ✅ Is the variable type constraint enforcing the expected shape?
  4. ✅ Are you using each.value.attr on a string set? (It should be each.value)
  5. ✅ Can you use optional() type modifier for non-required fields?

Prevention Tips

#
  • Always define explicit object types for for_each variables
  • Use optional() with defaults (Terraform 1.3+) for non-required attributes
  • Prefer try() over lookup() for nested attribute access
  • Add output blocks during development to inspect data structures
  • Run terraform validate to catch type errors before plan
#

Conclusion

#

The "unsupported attribute" error on each.value means the data structure doesn't match your code's expectations. Define type-safe variables with object() types, use optional() for non-required fields, and use try() or lookup() with defaults for flexibility. Always check whether for_each is iterating over strings or objects.

#for-each#attributes#maps

Share this article