Documentation Index Fetch the complete documentation index at: https://mintlify.com/hashicorp/terraform/llms.txt
Use this file to discover all available pages before exploring further.
Expressions
Expressions are used to compute values in Terraform. They can be simple literal values or complex combinations of references, operators, and function calls.
Types of Expressions
Literal Values
Direct value representations:
# Strings
name = "example"
multiline = << EOT
Line 1
Line 2
EOT
# Numbers
count = 5
pi = 3.14159
# Booleans
enabled = true
disabled = false
# Null
optional_value = null
References
Reference values from other parts of your configuration:
# Resource attributes
ami = aws_ami . ubuntu . id
subnet_id = aws_subnet . public . id
# Variables
instance_type = var . instance_type
region = var . aws_region
# Local values
common_tags = local . default_tags
az_count = local . availability_zone_count
# Data sources
vpc_id = data . aws_vpc . main . id
# Module outputs
endpoint = module . database . connection_string
# Path references
config_file = file ( " ${ path . module } /config.json" )
template = file ( " ${ path . root } /templates/init.sh" )
Operators
Arithmetic Operators
locals {
addition = 5 + 3 # 8
subtraction = 10 - 4 # 6
multiplication = 3 * 4 # 12
division = 20 / 4 # 5
modulo = 10 % 3 # 1
negation = - 5 # -5
}
Comparison Operators
locals {
equal = 5 == 5 # true
not_equal = 5 != 3 # true
less_than = 3 < 5 # true
less_or_equal = 5 <= 5 # true
greater_than = 5 > 3 # true
greater_or_equal = 5 >= 5 # true
}
Logical Operators
locals {
and_operation = true && false # false
or_operation = true || false # true
not_operation = ! false # true
# Combining operators
complex = (var . environment == "prod" ) && (var . enabled == true )
}
Conditional Expressions
Ternary operator for conditional logic:
instance_type = var . environment == "production" ? "t2.large" : "t2.micro"
count = var . create_resource ? 1 : 0
tags = merge (
var . common_tags ,
var . environment == "production" ? { Critical = "true" } : {}
)
Collection Operations
List and Tuple Access
# Index access
first_az = var . availability_zones [ 0 ]
second_az = var . availability_zones [ 1 ]
# Negative indices (from end)
last_az = var . availability_zones [ - 1 ]
# Length
az_count = length (var . availability_zones )
Map and Object Access
# Dot notation
region = var . config . region
# Bracket notation
region = var . config [ "region" ]
# With variables
value = var . settings [ var . key_name ]
Splat Expressions
Extract attributes from lists:
# For resources with count
instance_ids = aws_instance . web [ * ] . id
private_ips = aws_instance . web [ * ] . private_ip
# For resources with for_each
instance_ids = values (aws_instance . web )[ * ] . id
# Nested attribute access
vpc_ids = aws_subnet . private [ * ] . vpc_id
For Expressions
Transform and filter collections:
# Transform elements
uppercase_names = [ for name in var . names : upper (name)]
# With index
indexed_names = [ for i , name in var . names : " ${ i } : ${ name } " ]
# Filtering
production_instances = [
for instance in var . instances :
instance . id if instance . environment == "production"
]
# Transform map
port_map = {
for key , value in var . ports :
key => value + 1000
}
# Create map from list
instance_map = {
for instance in aws_instance . web :
instance. id => instance.private_ip
}
# Filtering maps
active_users = {
for username , user in var . users :
username => user if user . active
}
Grouping
# Group by attribute
instances_by_az = {
for instance in aws_instance . web :
instance. availability_zone => instance ...
}
String Templates
Interpolation
Embed expressions in strings:
greeting = "Hello, ${ var . name } !"
path = "/var/lib/ ${ var . service_name } /data"
url = "https:// ${ aws_instance . web . public_ip } : ${ var . port } "
String Directives
Control flow in strings:
# For loops
user_list = << EOT
Users:
%{ for user in var . users ~ }
- ${ user }
%{ endfor ~ }
EOT
# Conditionals
config = << EOT
%{ if var . debug_mode ~ }
debug = true
log_level = verbose
%{ else ~ }
debug = false
log_level = info
%{ endif ~ }
EOT
Heredoc Strings
Multi-line strings with indentation control:
# Standard heredoc
script = << EOT
#!/bin/bash
echo "Hello World"
EOT
# Indented heredoc (strips leading whitespace)
script = <<- EOT
#!/bin/bash
echo "Hello World"
EOT
Function Calls
Call built-in functions:
# String functions
uppercase = upper (var . name )
lowercase = lower (var . name )
joined = join ( ", " , var . list )
# Collection functions
list_length = length (var . items )
merged_map = merge (var . map1 , var . map2 )
flattened = flatten (var . nested_list )
# Numeric functions
absolute = abs ( - 5 )
ceiling = ceil ( 3.7 )
floor = floor ( 3.7 )
# Date functions
current_time = timestamp ()
future_time = timeadd ( timestamp (), "24h" )
# Encoding functions
base64_encoded = base64encode ( "hello" )
json_encoded = jsonencode ({ name = "value" })
# File functions
file_content = file ( " ${ path . module } /config.json" )
file_exists = fileexists ( " ${ path . module } /optional.txt" )
Type Conversions
Explicit type conversions:
# Convert to string
string_value = tostring ( 123 )
# Convert to number
number_value = tonumber ( "42" )
# Convert to bool
bool_value = tobool ( "true" )
# Convert to list
list_value = tolist ([ "a" , "b" ])
# Convert to set
set_value = toset ([ "a" , "b" , "a" ]) # Removes duplicates
# Convert to map
map_value = tomap ({ key = "value" })
Dynamic Blocks
Generate nested blocks dynamically:
resource "aws_security_group" "example" {
name = "example"
dynamic "ingress" {
for_each = var . ingress_rules
content {
from_port = ingress . value . from_port
to_port = ingress . value . to_port
protocol = ingress . value . protocol
cidr_blocks = ingress . value . cidr_blocks
}
}
}
With iterator:
dynamic "ingress" {
for_each = var . ingress_rules
iterator = rule
content {
from_port = rule . value . from_port
to_port = rule . value . to_port
protocol = rule . value . protocol
}
}
Special Values
Path References
path . module # Path to the current module
path . root # Path to the root module
path . cwd # Current working directory
terraform . workspace # Current workspace name
count . index # Current index in count
each . key # Current key in for_each
each . value # Current value in for_each
self .< ATTRIBUTE > # Current resource's attribute
Try and Can Functions
Handle errors gracefully:
# Try multiple expressions
value = try (
var . value ,
local . default_value ,
"fallback"
)
# Check if expression succeeds
is_valid = can ( regex ( "^[a-z]+$" , var . name ))
# Conditional based on validity
value = can (var . optional_value ) ? var . optional_value : "default"
Type Constraints
Type specifications for validation:
variable "example" {
type = object ({
name = string
age = number
active = bool
tags = map ( string )
items = list ( string )
config = object ({
enabled = bool
value = string
})
})
}
Optional Attributes
variable "server" {
type = object ({
name = string
port = optional ( number , 8080 ) # Optional with default
ssl = optional ( bool ) # Optional without default
})
}
References and Dependency Detection
Terraform automatically detects dependencies:
resource "aws_instance" "web" {
ami = data . aws_ami . ubuntu . id # Depends on data source
subnet_id = aws_subnet . public . id # Depends on subnet
instance_type = var . instance_type # Depends on variable
# Explicit reference creates dependency
user_data = templatefile ( "init.sh" , {
db_endpoint = aws_db_instance.main.endpoint
})
}
Expression Examples
Complex Conditionals
locals {
instance_type = (
var . environment == "production" ? "t2.large" :
var . environment == "staging" ? "t2.medium" :
"t2.micro"
)
}
Nested For Expressions
locals {
all_instance_ips = flatten ([
for az , instances in var . instances_by_az : [
for instance in instances : instance . ip
]
])
}
Map Merging
locals {
tags = merge (
var . common_tags ,
var . environment_tags ,
{
Name = var.instance_name
Terraform = "true"
}
)
}
locals {
# Filter and transform
active_user_emails = [
for username , user in var . users :
user . email if user . active && user . email != null
]
# Group and count
instances_per_az = {
for az in distinct ([ for i in aws_instance . web : i . availability_zone ]) :
az => length ([ for i in aws_instance . web : i if i . availability_zone == az ])
}
}
Best Practices
Use Locals for Complexity Move complex expressions to locals for readability and reusability.
Avoid Deep Nesting Break down deeply nested expressions into multiple steps.
Add Comments Document complex expressions to explain the logic.
Handle Errors Use try and can to handle potential errors gracefully.
Common Patterns
Conditional Resource Creation
resource "aws_eip" "web" {
count = var . create_public_ip ? 1 : 0
instance = aws_instance . web . id
}
Dynamic Tagging
tags = merge (
var . common_tags ,
{
Name = " ${ var . environment } - ${ var . service } "
Environment = var.environment
ManagedBy = "Terraform"
},
var . additional_tags
)
Safe Attribute Access
# Using try for optional attributes
endpoint = try (aws_db_instance . main . endpoint , "" )
# Using can to check existence
has_public_ip = can (aws_instance . web . public_ip )
Next Steps
Functions Explore built-in functions
Variables Use expressions with variables