| 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:
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.
~/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:
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:
cd ~/terraform-labs/terraform-functions/main && terraform consoleAt 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:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'lower("API-GW")' | terraform console -no-color"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:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'trimspace(" My App ")' | terraform console -no-color"My App"split and join are inverses for simple delimiters. Split a comma-separated tag string:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'split(",", "api,gw,v1")' | terraform console -no-colortolist([
"api",
"gw",
"v1",
])Join those tokens back with a hyphen:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'join("-", ["api", "gw", "v1"])' | terraform console -no-color"api-gw-v1"Initialize the lab directory, then apply so the string locals surface as outputs:
cd ~/terraform-labs/terraform-functions/main && terraform initApply the configuration to load the string function outputs:
cd ~/terraform-labs/terraform-functions/main && terraform apply -auto-approve -input=false -no-colorThe apply prints several string outputs; these three confirm the transforms:
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:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'min(1, 5, 3)' | terraform console -no-color1ceil rounds up; floor rounds down:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'ceil(2.1)' | terraform console -no-color3Test floor on a value just below the next integer:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'floor(2.9)' | terraform console -no-color2After 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:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'flatten([["a"], ["b", "c"]])' | terraform console -no-color[
"a",
"b",
"c",
]merge combines maps. When keys collide, the right-hand map wins:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'merge({env = "dev"}, {env = "prod", role = "api"})' | terraform console -no-color{
"env" = "prod"
"role" = "api"
}lookup is the idiomatic way to read optional map keys. When the key exists, you get its value:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'lookup({k = "v"}, "k", "missing")' | terraform console -no-color"v"When the key is absent, the third argument is returned instead:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'lookup({k = "v"}, "region", "missing")' | terraform console -no-color"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:
cd ~/terraform-labs/terraform-functions/main && terraform output -no-color collection_lookup"us-east-1"merge combined tags_map with { role = "lab" } during apply. The merged map and contains result appear in the full output list:
cd ~/terraform-labs/terraform-functions/main && terraform output -no-colorcollection_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:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'tostring(42)' | terraform console -no-color"42"toset removes duplicates while converting:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'toset(["a", "a"])' | terraform console -no-colortoset([
"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:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'jsonencode({a = 1})' | terraform console -no-color"{\"a\":1}"Decode it back to an HCL map:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'jsondecode("{\"a\":1}")' | terraform console -no-color{
"a" = 1
}The lab reads files/config.json with file, then jsondecode:
{
"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:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'base64encode("hi")' | terraform console -no-color"aGk="Decode the same payload to recover the original string:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'base64decode("aGk=")' | terraform console -no-color"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/:
files/
├── config.json
├── policy.yaml
├── greeting.tftpl
└── readme.txtgreeting.tftpl is a one-line template:
Hello, ${name}! Welcome to the Terraform functions lab.Check that readme.txt is present before you read it:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'fileexists("${path.module}/files/readme.txt")' | terraform console -no-colortrueRender the template with a map of variables:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'templatefile("${path.module}/files/greeting.tftpl", {name = "lab"})' | terraform console -no-color<<EOT
Hello, lab! Welcome to the Terraform functions lab.
EOTfileset finds JSON files for batch processing:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'fileset("${path.module}/files", "*.json")' | terraform console -no-colortoset([
"config.json",
])Confirm the template and fileset outputs from state:
cd ~/terraform-labs/terraform-functions/main && terraform output -no-color filesystem_template<<EOT
Hello, lab! Welcome to the Terraform functions lab.
EOTThe fileset glob result is a set of relative paths:
cd ~/terraform-labs/terraform-functions/main && terraform output -no-color filesystem_filesettoset([
"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:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'sha256("test")' | terraform console -no-color"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:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'formatdate("YYYY-MM-DD", timestamp())' | terraform console -no-color"2026-08-12"Add one hour:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'timeadd(timestamp(), "1h")' | terraform console -no-color"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:
output "uuid_value" {
value = uuid()
}Re-run plan after apply and the uuid_value output alone shows drift:
cd ~/terraform-labs/terraform-functions/main && terraform plan -no-color -input=falseChanges 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:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'try(tonumber("x"), 0)' | terraform console -no-color0Good uses:
try(tonumber(var.port), 8080)when callers sometimes pass unparsable stringstry(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
trylayers instead of fixing the variable type
The applied lab stores the fallback result in state:
cd ~/terraform-labs/terraform-functions/main && terraform output -no-color try_tonumber_fallback0can 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:
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:
lower(replace(trimspace(var.raw_name), " ", "-"))With var.raw_name = " My App ", console returns:
cd ~/terraform-labs/terraform-functions/main && printf '%s\n' 'lower(replace(trimspace(" My App "), " ", "-"))' | terraform console -no-color"my-app"Collection composition works the same way. To build a deduplicated tag list from two inputs:
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:
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/:
cd ~/terraform-labs/terraform-functions/errors/wrong-arg-type && terraform init && terraform validate -no-colorError: 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/:
cd ~/terraform-labs/terraform-functions/errors/missing-file && terraform init && terraform validate -no-colorError: 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 codeInvalid JSON — errors/bad-json/:
cd ~/terraform-labs/terraform-functions/errors/bad-json && terraform init && terraform validate -no-colorError: 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
- Functions — Terraform configuration language
- Strings — Terraform functions
- Collections — Terraform functions
- Encoding — Terraform functions
- Filesystem — Terraform functions
- Date and Time — Terraform functions
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.

