The Problem
Terraform is usually smart about types, but it draws a hard line at string interpolation. You will likely hit this error during a plan or apply when you try to shove a complex data structure where a simple string belongs:
Error: Invalid template interpolation value
on main.tf line 42, in resource "aws_instance" "example":
42: user_data = "echo ${var.config_map}"
|----------------
| var.config_map is object with 2 attributes
Cannot include the given value in a string template: string required.
The ${...} syntax has one job: injecting strings. If you pass it a list, map, or object, Terraform won't guess how to format it. It expects a flat string, and when it sees a multi-layered object instead, the process stops.
Common Scenarios
This isn't just a syntax quirk; it's a type mismatch. You'll often run into this when:
- Passing a map of environment variables into a
user_datashell script. - Trying to print a list of 5 private IP addresses into a configuration file.
- Injecting an entire resource object into a tag or metadata field.
Three Ways to Fix It
The solution depends on how you want your data to look once it's converted. Here are the standard approaches.
1. Use jsonencode() for Maps and Objects
If your destination (like an SSM parameter or a config file) expects structured data, use jsonencode(). This turns your HCL object into a valid JSON string automatically.
The Wrong Way:
resource "aws_ssm_parameter" "config" {
name = "app_settings"
type = "String"
value = "settings = ${var.my_map}" # This triggers the error
}
The Right Way:
resource "aws_ssm_parameter" "config" {
name = "app_settings"
type = "String"
value = jsonencode(var.my_map) # Returns "{\"key\":\"value\"}"
}
2. Use join() for Lists
Lists are tricky. If you have a list of strings, you usually want to merge them with a separator like a comma or a space.
Example:
resource "aws_instance" "web" {
# Join a list of 3 CIDR blocks into a single comma-separated string
user_data = <<-EOT
#!/bin/bash
echo "Allowed Sources: ${join(", ", var.external_ips)}"
EOT
}
3. Use HCL Directives for Complex Formatting
Sometimes jsonencode is too messy. If you are using templatefile() to generate a custom config (like an Nginx or HaProxy config), use a for loop directive inside the template itself.
Your template (config.tftpl):
%{ for name, ip in servers ~}
server ${name} ${ip}:8080
%{ endfor ~}
Your Terraform code:
content = templatefile("config.tftpl", {
servers = { "web01" = "10.0.1.5", "web02" = "10.0.1.6" }
})
Test Without Deploying
Don't wait for a slow terraform plan to see if your fix works. Use the built-in console for instant feedback:
- Run
terraform consolein your terminal. - Type your logic:
join(" | ", ["prod", "staging"]). - If it outputs
"prod | staging", your interpolation is safe.
Expert Tips
- Strict Typing: Define your variables clearly. Using
type = map(string)catches errors at the input stage rather than failing mid-deployment. - YAML Support: If JSON is too bulky for your logs,
yamlencode()works exactly like its JSON counterpart but outputs cleaner YAML. - Security: Be careful with
jsonencodeon objects containing secrets. If a variable is markedsensitive = true, Terraform will redact the entire string in your CLI output to keep passwords safe.

