TerraformPilot

Troubleshooting

Fix Terraform Error - Invalid Template Interpolation

Fix the Terraform invalid template interpolation error when embedding complex types in strings. Covers jsonencode, join, format, and type conversion patterns.

LLuca Berton2 min read

Quick Answer

#

You're trying to interpolate a complex type (list, map, or object) directly into a string template. Terraform can only interpolate scalar values (strings, numbers, booleans) into strings. Use jsonencode() to serialize complex types, join() for lists, or access individual elements.

The Error

#

When running terraform plan or terraform validate:

Error: Invalid template interpolation value
 
  on main.tf line 5, in resource "aws_instance" "web":
  5:   user_data = "Subnets: ${var.subnet_ids}"
 
Cannot include the given value in a string template: string required.
Error: Invalid template interpolation value
 
Cannot include the given value in a string template:
  the value is list of string, which does not have a single
  string representation.

What Causes This Error

#

Terraform string templates ("${...}") only accept values that can be represented as a single string. Lists, maps, sets, and objects can't be directly embedded.

Common Triggers

#
# BAD — list in string template
output "subnets" {
  value = "Subnets: ${var.subnet_ids}"  # var.subnet_ids is a list
}
 
# BAD — map in string template
output "config" {
  value = "Config: ${var.tags}"  # var.tags is a map
}
 
# BAD — object in string template
output "server" {
  value = "Server: ${aws_instance.web}"  # resource reference is an object
}

How to Fix It

#

Solution 1: Use jsonencode() for Complex Types

#

Convert any complex type to a JSON string:

# Lists
output "subnets" {
  value = "Subnets: ${jsonencode(var.subnet_ids)}"
}
# Output: Subnets: ["subnet-abc","subnet-def","subnet-ghi"]
 
# Maps
output "tags" {
  value = "Tags: ${jsonencode(var.tags)}"
}
# Output: Tags: {"Environment":"prod","Project":"myapp"}
 
# Objects
output "instance" {
  value = "Instance ID: ${jsonencode(aws_instance.web.id)}"
}

Solution 2: Use join() for Lists

#

Create a human-readable string from a list:

variable "subnet_ids" {
  type    = list(string)
  default = ["subnet-abc", "subnet-def", "subnet-ghi"]
}
 
# Comma-separated
output "subnets_csv" {
  value = "Subnets: ${join(", ", var.subnet_ids)}"
}
# Output: Subnets: subnet-abc, subnet-def, subnet-ghi
 
# Newline-separated
output "subnets_list" {
  value = "Subnets:\n${join("\n", var.subnet_ids)}"
}
 
# Custom delimiter
output "subnets_pipe" {
  value = join(" | ", var.subnet_ids)
}

Solution 3: Access Individual Elements

#

Reference specific items instead of the whole collection:

# Access list elements by index
output "first_subnet" {
  value = "Primary subnet: ${var.subnet_ids[0]}"
}
 
# Access map values by key
output "env_tag" {
  value = "Environment: ${var.tags["Environment"]}"
}
 
# Access resource attributes
output "instance_info" {
  value = "Instance ${aws_instance.web.id} at ${aws_instance.web.private_ip}"
}

Solution 4: Use format() for Formatted Output

#
# format() for printf-style strings
output "server_info" {
  value = format("Server %s: type=%s, ip=%s",
    aws_instance.web.id,
    aws_instance.web.instance_type,
    aws_instance.web.private_ip
  )
}
 
# formatlist() for lists
output "tagged_subnets" {
  value = formatlist("subnet: %s", var.subnet_ids)
}
# Output: ["subnet: subnet-abc", "subnet: subnet-def"]

Solution 5: Use templatefile() for Complex Templates

#

For longer templates with embedded logic:

# templates/config.tftpl
%{ for subnet in subnets ~}
subnet ${subnet}
%{ endfor ~}
 
# main.tf
resource "aws_instance" "web" {
  user_data = templatefile("${path.module}/templates/config.tftpl", {
    subnets = var.subnet_ids
    tags    = var.tags
  })
}

Template file syntax supports loops and conditionals:

# templates/hosts.tftpl
%{ for name, ip in servers ~}
${ip} ${name}
%{ endfor ~}
 
# Variables:
# servers = {"web" = "10.0.1.10", "db" = "10.0.2.20"}
 
# Output:
# 10.0.1.10 web
# 10.0.2.20 db

Solution 6: Don't Interpolate When Not Needed

#

If you're just passing a value, don't wrap it in a string template:

# BAD — unnecessary interpolation
output "subnet_id" {
  value = "${var.subnet_ids[0]}"
}
 
# GOOD — direct reference
output "subnet_id" {
  value = var.subnet_ids[0]
}
 
# BAD — interpolating a list into a list context
resource "aws_instance" "web" {
  vpc_security_group_ids = ["${var.security_group_ids}"]  # ERROR
}
 
# GOOD — direct reference
resource "aws_instance" "web" {
  vpc_security_group_ids = var.security_group_ids
}

Type Conversion Quick Reference

#
Source TypeTo StringFunction
list(string)"a, b, c"join(", ", list)
list(any)'["a","b"]'jsonencode(list)
map(string)'{"k":"v"}'jsonencode(map)
number"42"tostring(42) or just "${42}"
bool"true"tostring(true) or just "${true}"
objectJSON stringjsonencode(object)
Specific elementStringlist[0] or map["key"]

Common Patterns

#
# User data script with list values
resource "aws_instance" "web" {
  user_data = <<-EOF
    #!/bin/bash
    # Install packages
    apt-get install -y ${join(" ", var.packages)}
 
    # Set environment variables
    %{ for key, value in var.env_vars ~}
    export ${key}="${value}"
    %{ endfor ~}
  EOF
}
 
# Tags from a map
resource "aws_instance" "web" {
  tags = merge(var.common_tags, {
    Name = "web-server"
  })
  # Don't interpolate the map — pass it directly to tags
}
 
# Log output with multiple values
output "summary" {
  value = format(
    "Deployed %d instances in %s (VPC: %s)",
    length(aws_instance.web),
    var.region,
    aws_vpc.main.id
  )
}

Troubleshooting Checklist

#
  1. ✅ What type is the value you're interpolating? (list, map, object?)
  2. ✅ Can you use join() for a list?
  3. ✅ Can you use jsonencode() for a complex type?
  4. ✅ Do you actually need string interpolation, or can you pass the value directly?
  5. ✅ Would format() or templatefile() be cleaner?

Prevention Tips

#
  • Don't wrap single values in "${...}" — Terraform allows direct references
  • Use jsonencode() as default for any complex type in a string context
  • Prefer templatefile() over heredoc for complex user data scripts
  • Define clear variable types — knowing the type prevents interpolation mistakes
  • Run terraform validate — catches type errors without cloud API calls
#

Conclusion

#

Terraform strings can only embed scalar values — not lists, maps, or objects. Use join() for lists, jsonencode() for complex types, format() for structured output, and templatefile() for scripts. Better yet, avoid unnecessary interpolation and pass values directly when the receiving argument accepts the correct type.

#templates#interpolation#types

Share this article