Terraform Functions with Practical Examples

Tested on Ubuntu 26.04 LTS (Resolute Raccoon)
Package terraform 1.15.8-1
Applies to Any host with Terraform installed
Lab environment Single Ubuntu VM with Terraform — Terraform lab environment on Ubuntu
Privilege Normal user; sudo only if Terraform is not installed yet
Scope Terraform built-in function syntax, string and numeric helpers, collection functions, type conversion, jsonencode and yamlencode, base64, file and templatefile, brief hash utilities, timestamp and uuid, try and can, function composition, terraform console testing, and common function errors. Does not cover every HashiCorp function, provider-defined functions, or a full expressions tutorial.
Related guides Terraform expressions
Terraform data types
Terraform variables
Terraform locals
Terraform Associate certification course

Terraform expressions combine literals, variables, and operators. Functions are the reusable transforms inside those expressions: normalize a hostname, merge tag maps, decode JSON from a file, or fall back when a conversion might fail.

Every function follows the same call shape:

text
function_name(argument1, argument2)

Arguments can be literals, input variables, locals, resource attributes, or nested calls such as lower(trimspace(var.name)). Return types follow Terraform data types: strings, numbers, booleans, lists, sets, maps, and tuples.

This guide groups the functions you will reach for most often on Terraform 1.15.8. You will test each category in terraform console and wire a few into a small configuration so plan and apply show real outputs.

NOTE
Work in ~/terraform-labs/terraform-functions/ on the Terraform lab environment on Ubuntu. Run terraform init in each subdirectory before plan, apply, or console. Examples use the built-in terraform_data resource so you do not need cloud credentials.

How Terraform function calls work

Functions are not standalone statements. They appear on the right-hand side of arguments, in locals, in output blocks, and inside for expressions.

A practical naming pipeline might look like this:

hcl
lower(replace(trimspace(var.raw_name), " ", "-"))

Read it inside out: trim whitespace, replace spaces with hyphens, then lowercase the result. The lab stores that pattern in local.slug and exposes it as composed_slug.

Open an interactive session whenever you want to experiment without editing files:

bash
cd ~/terraform-labs/terraform-functions/main && terraform console

At the > prompt, type an expression and press Enter. Exit with Ctrl+D or exit. For copy-paste captures in this article, a one-liner pipe works the same way:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'lower("API-GW")' | terraform console -no-color
output
"api-gw"

The quotes in console output mean Terraform returned a string value.


String and numeric functions

String functions

String functions shape labels, DNS-safe names, and human-readable messages. The lab at ~/terraform-labs/terraform-functions/main/ declares var.raw_name with extra spaces so trimspace and composition have something realistic to chew on.

Function Purpose Console example
lower Lowercase lower("API-GW")"api-gw"
upper Uppercase upper("dev")"DEV"
trimspace Strip leading and trailing whitespace trimspace(" x ")"x"
replace Substitute substrings replace("a-b", "-", "_")"a_b"
split Split string into list split(",", "a,b,c")
join Join list into string join("-", ["a", "b"])"a-b"
format Printf-style formatting format("hello %s", "world")

Try trimspace on the padded sample name:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'trimspace("  My App  ")' | terraform console -no-color
output
"My App"

split and join are inverses for simple delimiters. Split a comma-separated tag string:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'split(",", "api,gw,v1")' | terraform console -no-color
output
tolist([
  "api",
  "gw",
  "v1",
])

Join those tokens back with a hyphen:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'join("-", ["api", "gw", "v1"])' | terraform console -no-color
output
"api-gw-v1"

Initialize the lab directory, then apply so the string locals surface as outputs:

bash
cd ~/terraform-labs/terraform-functions/main && terraform init

Apply the configuration to load the string function outputs:

bash
cd ~/terraform-labs/terraform-functions/main && terraform apply -auto-approve -input=false -no-color

The apply prints several string outputs; these three confirm the transforms:

output
string_lower = "api-gw"
string_join = "api-gw-v1"
composed_slug = "my-app"

composed_slug is the chained lower(replace(trimspace(...))) expression you will reuse in the composition section later.

Numeric functions

Numeric helpers appear in capacity calculations, replica bounds, and rounding display values. This lesson covers a small exam-relevant set only.

Function Example Result
min min(1, 5, 3) 1
max max(1, 5, 3) 5
ceil ceil(2.1) 3
floor floor(2.9) 2

Confirm min picks the smallest argument:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'min(1, 5, 3)' | terraform console -no-color
output
1

ceil rounds up; floor rounds down:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'ceil(2.1)' | terraform console -no-color
output
3

Test floor on a value just below the next integer:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'floor(2.9)' | terraform console -no-color
output
2

After apply, numeric_min = 1 and numeric_max = 5 appear in the output block alongside the rounding results.


Collection and type conversion functions

Collection functions

Collection functions operate on lists, sets, and maps. The lab variables list_a, list_b, nested_lists, tags_map, and lookup_map supply realistic shapes.

Function Typical use
length Count elements
concat Join lists or tuples
flatten Collapse nested lists
distinct Remove duplicates from a list
compact Drop empty strings from a list
merge Combine maps (later keys override)
lookup Safe map access with default
keys / values Enumerate map entries
contains Test list or set membership

flatten turns nested lists into one list:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'flatten([["a"], ["b", "c"]])' | terraform console -no-color
output
[
  "a",
  "b",
  "c",
]

merge combines maps. When keys collide, the right-hand map wins:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'merge({env = "dev"}, {env = "prod", role = "api"})' | terraform console -no-color
output
{
  "env" = "prod"
  "role" = "api"
}

lookup is the idiomatic way to read optional map keys. When the key exists, you get its value:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'lookup({k = "v"}, "k", "missing")' | terraform console -no-color
output
"v"

When the key is absent, the third argument is returned instead:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'lookup({k = "v"}, "region", "missing")' | terraform console -no-color
output
"missing"

Bracket indexing map["region"] would have failed on the missing key. Reserve direct indexing for keys you know must exist.

Query the applied lookup output:

bash
cd ~/terraform-labs/terraform-functions/main && terraform output -no-color collection_lookup
output
"us-east-1"

merge combined tags_map with { role = "lab" } during apply. The merged map and contains result appear in the full output list:

bash
cd ~/terraform-labs/terraform-functions/main && terraform output -no-color
output
collection_contains = true
collection_lookup = "us-east-1"
collection_merge = {
  "env" = "dev"
  "role" = "lab"
  "team" = "platform"
}

Type conversions

Conversion functions make types explicit when Terraform cannot infer them automatically. They pair directly with the constraints in Terraform data types.

Function Example Result type
tostring tostring(42) string
tonumber tonumber("42") number
tobool tobool("true") bool
tolist tolist(["a"]) list
toset toset(["a", "a"]) set (deduplicated)
tomap tomap({ a = 1 }) map

Convert a number to a string for a tag value:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'tostring(42)' | terraform console -no-color
output
"42"

toset removes duplicates while converting:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'toset(["a", "a"])' | terraform console -no-color
output
toset([
  "a",
])

tonumber only accepts decimal strings. Invalid input fails evaluation — use try later when you need a fallback.


Encoding and filesystem functions

JSON, YAML, and Base64

Encoding functions serialize structured values to strings (or back). They are useful for policies, user data, and inline JSON — not for secrecy.

Function Direction
jsonencode / jsondecode HCL ↔ JSON string
yamlencode / yamldecode HCL ↔ YAML string
base64encode / base64decode Binary-safe string encoding

Encode a small map as JSON:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'jsonencode({a = 1})' | terraform console -no-color
output
"{\"a\":1}"

Decode it back to an HCL map:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'jsondecode("{\"a\":1}")' | terraform console -no-color
output
{
  "a" = 1
}

The lab reads files/config.json with file, then jsondecode:

json
{
  "service": "api-gw",
  "port": 8080,
  "enabled": true
}

After apply, encoding_jsondecode_service = "api-gw" confirms the decode path.

Base64 changes representation only — it is not encryption:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'base64encode("hi")' | terraform console -no-color
output
"aGk="

Decode the same payload to recover the original string:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'base64decode("aGk=")' | terraform console -no-color
output
"hi"

Anyone can decode that string without a key. Store secrets with your platform's secret manager, not base64encode.

file, fileset, fileexists, and templatefile

Filesystem functions accept a path expression. Use ${path.module} when the file belongs to the module so the path remains tied to that module's source directory.

Functions such as file() and fileexists() do not participate in Terraform's dependency graph, so the target file must already exist before the Terraform run begins. They cannot wait for a resource to create the file during apply — use resource attributes for values that only exist after apply.

Function Purpose
file Read entire file as string
fileexists Test whether a path exists
fileset Glob files in a directory
templatefile Render a template with variables

The lab keeps supporting files under files/:

text
files/
├── config.json
├── policy.yaml
├── greeting.tftpl
└── readme.txt

greeting.tftpl is a one-line template:

text
Hello, ${name}! Welcome to the Terraform functions lab.

Check that readme.txt is present before you read it:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'fileexists("${path.module}/files/readme.txt")' | terraform console -no-color
output
true

Render the template with a map of variables:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'templatefile("${path.module}/files/greeting.tftpl", {name = "lab"})' | terraform console -no-color
output
<<EOT
Hello, lab! Welcome to the Terraform functions lab.

EOT

fileset finds JSON files for batch processing:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'fileset("${path.module}/files", "*.json")' | terraform console -no-color
output
toset([
  "config.json",
])

Confirm the template and fileset outputs from state:

bash
cd ~/terraform-labs/terraform-functions/main && terraform output -no-color filesystem_template
output
<<EOT
Hello, lab! Welcome to the Terraform functions lab.

EOT

The fileset glob result is a set of relative paths:

bash
cd ~/terraform-labs/terraform-functions/main && terraform output -no-color filesystem_fileset
output
toset([
  "config.json",
])

Hash and checksum helpers

Terraform exposes md5, sha256, filemd5, and filesha256 for checksums and change detection. They are not a substitute for secret storage or transport security.

Hash a literal string in console:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'sha256("test")' | terraform console -no-color
output
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"

The lab also exposes hash_md5 and hash_sha256 outputs from main.tf. Use these when you need a stable fingerprint of file contents or a trigger value — not when you need confidentiality.


Time and identifier functions

timestamp, formatdate, and timeadd

Time functions format timestamps and perform calendar arithmetic. Several of them return a new value on every evaluation, which affects plan stability.

Function Role
timestamp Current UTC time as RFC 3339 string
formatdate Format a timestamp with a template
timeadd Add a duration to a timestamp

Format today's date from the current timestamp:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'formatdate("YYYY-MM-DD", timestamp())' | terraform console -no-color
output
"2026-08-12"

Add one hour:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'timeadd(timestamp(), "1h")' | terraform console -no-color
output
"2026-08-12T03:08:43Z"

Because timestamp() changes between runs, an output that calls it directly may show as changed on every plan even when infrastructure is unchanged. Prefer static defaults for resource arguments; reserve live timestamps for triggers, annotations, or values you intentionally want to refresh.

uuid and uuidv5

uuid() generates a UUID-formatted identifier from random bytes. Terraform's uuid() output is not RFC-compliant and produces a new value each time it is evaluated. uuidv5(namespace, name) is a deterministic RFC 4122 version 5 UUID derived from a namespace and name — useful when you need the same ID every time for the same inputs.

The lab exposes a raw uuid() output:

hcl
output "uuid_value" {
  value = uuid()
}

Re-run plan after apply and the uuid_value output alone shows drift:

bash
cd ~/terraform-labs/terraform-functions/main && terraform plan -no-color -input=false
output
Changes to Outputs:
  ~ uuid_value = "2cac7339-3dbf-a41c-eb0e-58e327425166" -> (known after apply)

You can apply this plan to save these new output values to the Terraform
state, without changing any real infrastructure.

That drift is expected. Avoid uuid() directly in resource arguments because it produces a new value on each evaluation and therefore causes spurious diffs. For a random value that should persist in state, HashiCorp recommends resources from the Random provider instead. For stable identifiers derived from names, use uuidv5 with a fixed namespace. Advanced teams sometimes pair lifecycle { ignore_changes = [...] } with non-deterministic values, but that is an exception rather than the default fix.


Error-handling functions: try and can

try and can handle expressions that might fail at evaluation time, but they solve different problems. try returns the first expression that evaluates successfully. can converts a single expression into true or false. can only catches dynamic evaluation errors — not expressions that are statically invalid.

try for normalization and fallbacks

Use try when you want a fallback value if a conversion or lookup might fail:

Function Behavior
try(expr1, expr2, …) Evaluate expressions left to right; return the first that succeeds

Return a numeric fallback when tonumber cannot parse:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'try(tonumber("x"), 0)' | terraform console -no-color
output
0

Good uses:

  • try(tonumber(var.port), 8080) when callers sometimes pass unparsable strings
  • try(local.config["optional_key"], null) when a map key may be absent

Poor uses:

  • Wrapping arbitrary provider API calls in try() to hide misconfiguration
  • Nesting many try layers instead of fixing the variable type

The applied lab stores the fallback result in state:

bash
cd ~/terraform-labs/terraform-functions/main && terraform output -no-color try_tonumber_fallback
output
0

can for validation tests

HashiCorp recommends can() mainly for simple variable validation tests. A typical pattern checks whether a value parses before you rely on it elsewhere:

hcl
variable "timestamp" {
  type = string

  validation {
    condition     = can(formatdate("", var.timestamp))
    error_message = "timestamp must be a valid RFC 3339 value."
  }
}

can() returns true when the expression inside evaluates without error and false otherwise. Reserve it for validation rules and similar guard expressions — not as a general-purpose substitute for try() when you need a fallback value.


Compose Terraform functions

Real configurations chain functions. The slug pattern is a common one:

hcl
lower(replace(trimspace(var.raw_name), " ", "-"))

With var.raw_name = " My App ", console returns:

bash
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'lower(replace(trimspace("  My App  "), " ", "-"))' | terraform console -no-color
output
"my-app"

Collection composition works the same way. To build a deduplicated tag list from two inputs:

hcl
distinct(compact(concat(var.list_a, var.list_b)))

Read nested calls from the inside out: concatenate, drop empty strings, then remove duplicates.

Keep chains short. When a pipeline grows past three transforms, split it across named locals so the next reader sees intent:

hcl
locals {
  trimmed = trimspace(var.raw_name)
  dashed  = replace(local.trimmed, " ", "-")
  slug    = lower(local.dashed)
}

That refactor mirrors what you might do after proving an expression in console.


Common Terraform function errors

Most function failures are type mismatches, bad arity, or missing files. The errors/ tree under ~/terraform-labs/terraform-functions/ reproduces each pattern.

Symptom Likely cause Fix
Invalid function argument on tonumber String is not a decimal number Validate input; use try with a default
no file exists at on file Path wrong or file not in module Ship the file with config; use path.module
jsondecode / yamldecode failed Malformed payload Fix source JSON/YAML; test with console
lookup vs map[key] Missing key Pass a default to lookup or guard with contains(keys(map), key)
formatdate failed Invalid template token Compare against HashiCorp date format spec
Perpetual plan changes on uuid() or timestamp() Non-deterministic function in output or resource arg Use Random provider resources for persisted random values; use uuidv5 for deterministic IDs

Wrong argument type — errors/wrong-arg-type/:

bash
cd ~/terraform-labs/terraform-functions/errors/wrong-arg-type && terraform init && terraform validate -no-color
output
Error: Invalid function argument

  on main.tf line 6, in locals:
   6:   bad = tonumber("not-numeric")
    ├────────────────
    │ while calling tonumber(v)

Invalid value for "v" parameter: cannot convert "not-numeric" to number;
given string must be a decimal representation of a number.

Missing file — errors/missing-file/:

bash
cd ~/terraform-labs/terraform-functions/errors/missing-file && terraform init && terraform validate -no-color
output
Error: Invalid function argument

  on main.tf line 6, in locals:
   6:   content = file("${path.module}/nope.txt")
    ├────────────────
    │ while calling file(path)
    │ path.module is "."

Invalid value for "path" parameter: no file exists at "./nope.txt"; this
function works only with files that are distributed as part of the
configuration source code

Invalid JSON — errors/bad-json/:

bash
cd ~/terraform-labs/terraform-functions/errors/bad-json && terraform init && terraform validate -no-color
output
Error: Error in function call

  on main.tf line 6, in locals:
   6:   parsed = jsondecode("{bad")
    ├────────────────
    │ while calling jsondecode(str)

Call to function "jsondecode" failed: invalid character 'b'.

Official References


Summary

You worked through Terraform's built-in function syntax and tested categories in terraform console before wiring them into a single lab module. String and numeric helpers normalize names and bounds; collection functions merge maps and safely read keys with lookup; conversion and encoding functions bridge HCL values and serialized JSON or YAML.

Filesystem functions read static module files with ${path.module}, and templatefile renders parameterized templates. Those functions do not join the dependency graph, so files must exist before the run starts. Use try for fallback values when an expression might fail; reserve can for validation rules. Composition chains such as lower(replace(trimspace(...))) stay readable when you build them inside out or split them across locals.

Watch non-deterministic functions: timestamp() and uuid() can cause perpetual plan diffs when you use them directly in outputs or resource arguments — uuid() is not RFC-compliant and re-evaluates every time. For persisted random values, use the Random provider. Base64 encoding is representation only — not encryption. Next, combine functions inside Terraform expressions or tighten input shapes with Terraform data types.


Frequently Asked Questions

1. What is the syntax for Terraform functions?

Functions use function_name(argument1, argument2) syntax inside expressions. Arguments can be literals, variables, locals, resource attributes, or nested function calls.

2. How do I test Terraform functions without applying infrastructure?

Run terraform console in an initialized directory and type expressions at the prompt. Console evaluates functions the same way plan and apply do, which makes it ideal for quick experiments.

3. What is the difference between lookup and map indexing in Terraform?

lookup(map, key, default) returns a default when the key is missing. Bracket indexing map[key] fails if the key does not exist. Use lookup when optional keys are normal.

4. Is base64encode encryption in Terraform?

No. base64encode and base64decode only change representation. Anyone can decode base64 without a secret key. Use proper secret management for sensitive values.

5. When should I use try versus can in Terraform?

try returns the first expression that evaluates successfully and is useful for normalizing optional or variable-shaped data. can converts a potentially failing expression into true or false and is mainly useful in validation rules. Prefer try for fallback values.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)