Skip to content
Infrastructure as Code iac hcl 5 min read

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"
}
FunctionSignatureExample result
formatformat(spec, values...)format("%03d", 7)"007"
joinjoin(sep, list)join("/", ["a","b"])"a/b"
splitsplit(sep, string)split(",", "a,b")["a","b"]
replacereplace(str, find, repl)replace("a.b", ".", "_")"a_b"
trimspacetrimspace(str)trimspace(" x ")"x"
lower / upperlower(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
}
FunctionPurposeExample
lookupMap value with defaultlookup(m, "k", "fallback")
mergeCombine mapsmerge(a, b)
concatJoin listsconcat([1], [2,3])[1,2,3]
flattenCollapse nestingflatten([[1],[2]])[1,2]
keys / valuesMap keys/valueskeys({a=1})["a"]
containsMembership testcontains([1,2], 2)true
coalesceFirst non-nullcoalesce(null, "x")"x"

Tip: merge is the idiomatic way to layer module-level default tags with per-resource overrides — keep your defaults in a local and 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: file and templatefile are 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())
}
FunctionPurpose
cidrsubnetCarve a subnet from a CIDR block
cidrhostCompute a host IP within a range
timestampCurrent UTC time (RFC 3339)
formatdateFormat a timestamp string
timeaddOffset 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 console to verify a function’s behaviour before committing it to your configuration.
  • Use jsonencode for IAM policies and JSON arguments instead of heredoc strings — it eliminates escaping bugs and keeps the structure readable.
  • Reach for merge to compose default and override tag maps rather than duplicating tag blocks across resources.
  • Use templatefile over inline string interpolation for any multi-line script or config; keep templates in version-controlled .tftpl files.
  • Guard lookup and 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.
Last updated June 14, 2026
Was this helpful?