Built-in Functions
Terraform ships with a rich library of built-in functions you can call inside any expression to transform and combine values. There is no way to define your own functions in HCL, so this catalogue is the toolbox you reach for when wiring inputs into resource arguments — formatting names, merging maps of tags, decoding files, or rendering templates. Functions are pure: given the same arguments they always return the same result, which keeps your plans deterministic. This page tours the most useful functions by category, with a quick way to experiment from your terminal.
Calling functions and the console
A function call is just name(arg1, arg2, ...) and can appear anywhere an expression is valid. The fastest way to learn the behaviour of any function is the interactive console, which evaluates expressions against your current state and variables.
terraform console
Output:
> upper("us-east-1")
"US-EAST-1"
> length(["a", "b", "c"])
3
> max(5, 12, 7)
12
The console also works under OpenTofu via tofu console, and supports every function listed below.
String functions
String functions cover formatting, case conversion, trimming, and substitution. format follows a printf-style verb syntax, join/split convert between lists and delimited strings, and replace accepts either a literal or a /regex/ pattern.
locals {
region = "us-east-1"
env = "prod"
bucket = format("%s-%s-assets", local.env, local.region) # "prod-us-east-1-assets"
csv = join(",", ["web", "api", "worker"]) # "web,api,worker"
parts = split("-", local.region) # ["us", "east", "1"]
sanitized = replace("my app name", " ", "-") # "my-app-name"
}
| Function | Signature | Example result |
|---|---|---|
format | format(spec, values...) | format("%03d", 7) → "007" |
join | join(sep, list) | join("/", ["a","b"]) → "a/b" |
split | split(sep, string) | split(",", "a,b") → ["a","b"] |
replace | replace(str, find, repl) | replace("a.b", ".", "_") → "a_b" |
trimspace | trimspace(str) | trimspace(" x ") → "x" |
lower / upper | lower(str) | upper("abc") → "ABC" |
Collection functions
Collection functions manipulate lists, sets, and maps. lookup reads a map key with a fallback default, merge combines maps (later keys win), concat appends lists, and flatten collapses nested lists into one.
locals {
common_tags = {
Project = "devcraftly"
Owner = "platform"
}
# merge: later maps override earlier keys
bucket_tags = merge(local.common_tags, {
Owner = "data-team"
Tier = "storage"
})
ports = concat([80, 443], [8080]) # [80, 443, 8080]
subnets = flatten([["10.0.1.0/24"], ["10.0.2.0/24"]])
team = lookup(local.common_tags, "Owner", "unassigned") # "platform"
}
resource "aws_s3_bucket" "assets" {
bucket = "devcraftly-assets"
tags = local.bucket_tags
}
| Function | Purpose | Example |
|---|---|---|
lookup | Map value with default | lookup(m, "k", "fallback") |
merge | Combine maps | merge(a, b) |
concat | Join lists | concat([1], [2,3]) → [1,2,3] |
flatten | Collapse nesting | flatten([[1],[2]]) → [1,2] |
keys / values | Map keys/values | keys({a=1}) → ["a"] |
contains | Membership test | contains([1,2], 2) → true |
coalesce | First non-null | coalesce(null, "x") → "x" |
Tip:
mergeis the idiomatic way to layer module-level default tags with per-resource overrides — keep your defaults in alocaland merge them at each resource.
Encoding functions
Encoding functions serialize and decode data so it can cross a boundary — into a JSON document, a base64-encoded user-data script, or a YAML config. jsonencode is invaluable for IAM policies and any provider argument that expects a JSON string.
resource "aws_iam_policy" "read_only" {
name = "s3-read-only"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["s3:GetObject", "s3:ListBucket"]
Resource = ["arn:aws:s3:::devcraftly-assets/*"]
}]
})
}
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.micro"
user_data = base64encode(<<-EOT
#!/bin/bash
echo "starting up" > /var/log/boot.log
EOT
)
}
Companion decoders include jsondecode, base64decode, and yamldecode for reading structured data back into HCL values.
Filesystem functions
Filesystem functions read files from disk at plan time. file returns raw contents, while templatefile renders a template with a map of variables — the modern replacement for the deprecated template_file data source.
resource "aws_instance" "app" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.small"
user_data = templatefile("${path.module}/init.tftpl", {
app_port = 8080
env = "prod"
})
}
# init.tftpl
#!/bin/bash
export APP_ENV=${env}
docker run -p ${app_port}:${app_port} myapp:latest
Warning:
fileandtemplatefileare evaluated during planning, so the referenced file must already exist on disk — you cannot read a file that another resource generates during the same apply.
IP and date/time functions
Networking functions compute CIDR ranges, and time functions stamp resources or build rotation logic.
locals {
vpc_cidr = "10.0.0.0/16"
subnet_a = cidrsubnet(local.vpc_cidr, 8, 0) # "10.0.0.0/24"
subnet_b = cidrsubnet(local.vpc_cidr, 8, 1) # "10.0.1.0/24"
host_ip = cidrhost(local.subnet_a, 5) # "10.0.0.5"
created_at = formatdate("YYYY-MM-DD", timestamp())
}
| Function | Purpose |
|---|---|
cidrsubnet | Carve a subnet from a CIDR block |
cidrhost | Compute a host IP within a range |
timestamp | Current UTC time (RFC 3339) |
formatdate | Format a timestamp string |
timeadd | Offset a timestamp |
Because timestamp changes on every run, scope it to resources that explicitly need it (or use a time_static resource) to avoid spurious diffs.
Best practices
- Prefer the interactive
terraform consoleto verify a function’s behaviour before committing it to your configuration. - Use
jsonencodefor IAM policies and JSON arguments instead of heredoc strings — it eliminates escaping bugs and keeps the structure readable. - Reach for
mergeto compose default and override tag maps rather than duplicating tag blocks across resources. - Use
templatefileover inline string interpolation for any multi-line script or config; keep templates in version-controlled.tftplfiles. - Guard
lookupand map access with a default value to make missing keys fail gracefully instead of erroring. - Avoid
timestamp()in arguments that feed long-lived resources, since it forces a diff on every plan. - Remember functions are pure and evaluated at plan time — they cannot read values that only exist after apply.