Functions
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages. |
Functions are how the Terraform language computes things it has no operator for: building a name from parts, deriving
subnets from a network range, reading a file, hashing a value, turning a data structure into JSON, or recovering from
a missing attribute. You cannot define your own functions in a configuration (the only way to add one is to write a
provider that exposes it), so the built-in library, plus whatever the providers in use contribute, is the whole
vocabulary. This page describes that library as documented for Terraform 1.17 and OpenTofu 1.13: how a call is
written, every category of functions with Bookshelf examples, the regular expression, file and template functions,
the functions that handle sensitive and ephemeral values, the functions and symbol that the built-in terraform
provider offers, which functions are pure and which are not, how to experiment with them in terraform console, and
what changed in recent releases.
It follows Types and Expressions, which introduces the call syntax, conversion rules and string templates, and it is used by every later page that builds values from other values, in particular Variables, Outputs and Locals, Meta-Arguments: count, for_each and depends_on and Lifecycle, Validation and Checks. The treatment of secrets that goes beyond the functions on this page is in Sensitive Data, Ephemeral Values and Write-Only Arguments.
All examples use the Bookshelf scenario (catalog-api, reviews-api, web, PostgreSQL, Redis, a covers bucket).
Unless an example says otherwise it needs no provider and no cloud account: the functions are evaluated by Terraform
itself, so the configurations below run with terraform init and terraform apply on a laptop.
How a function call works
A call is the function name, an opening parenthesis, comma-separated arguments and a closing parenthesis. It is an
expression, so it can appear wherever an expression is accepted: in an argument, a local value, an output, a
for expression, a condition or inside another call. The name is always a plain identifier; there is no method syntax
and no way to pass a function around as a value.
terraform {
required_version = ">= 1.15"
}
locals {
# A call can be nested in another call and mixed with operators and conditionals.
replicas = max(2, ceil(450 / 100))
label = upper(substr("catalog-api", 0, 7))
}
output "replicas" {
value = local.replicas
}
output "label" {
value = local.label
}
Four general rules explain most surprises:
-
Argument expansion. A list, set or tuple can be passed as separate arguments by following it with
...(three ASCII periods) in the call, but only in a function call:max([12, 54, 3]...)is54. -
Types are converted where possible. Arguments are converted to the parameter types the function declares. A function that takes
numberaccepts the string three in quotes, andcoalesce(1, "hello")returns a string, not a number. The conversion rules are those of Types and Expressions. -
Marks travel with values. Passing a sensitive value to a function marks the result sensitive, and an unknown argument usually makes the result unknown (shown as
(known after apply)). The functions that deliberately remove or inspect a mark are described in Functions for sensitive and ephemeral values. -
Support depends on the file type. Standard
.tfand.tf.jsonfiles accept every function. Other configuration files, such as the Terraform Stacks.tfcomponent.hcland.tfdeploy.hclfiles, support only a subset; the function overview lists which functions work in which file type.
Functions contributed by providers are called with a prefixed name, provider::<local_name>::<function>(...), where the
local name is the key under required_providers. They are covered in Provider-defined functions and the built-in terraform provider.
The function library by category
The tables below list every function in the Terraform 1.17 documentation by category, with the signature for the ones used in the examples. Where OpenTofu has a function that Terraform lacks, the table says so; the complete list of differences is in Terraform and OpenTofu differences.
Numeric functions
| Function | Purpose |
|---|---|
|
Absolute value of a number. |
|
Round up or down to a whole number. |
|
Logarithm of a number in a given base. Since Terraform 1.17 it raises an error instead of returning infinity. |
|
Greatest or smallest of one or more numbers; expand a list with |
|
Parse a string as an integer in a base between 2 and 62: |
|
First argument raised to the power of the second. Also an error on an infinite result since 1.17. |
|
|
The arithmetic operators cover addition, subtraction, multiplication, division and remainder; the functions above cover rounding, extremes and exponents. The typical use in infrastructure code is sizing: a number of replicas, a disk size or a count of subnets is computed from a load estimate and then clamped to a minimum.
variable "expected_rps" {
type = number
description = "Expected peak requests per second for catalog-api."
default = 450
}
variable "rps_per_replica" {
type = number
description = "Requests per second one catalog-api replica can serve."
default = 100
}
variable "min_replicas" {
type = number
default = 2
}
locals {
# 450 / 100 = 4.5, ceil gives 5, and the floor of two replicas never applies here.
replicas_needed = max(var.min_replicas, ceil(var.expected_rps / var.rps_per_replica))
# Gibibytes to bytes for an argument that expects bytes: 2^30.
one_gib = pow(2, 30)
}
Some results from terraform console:
> max(5, 12, 9)
12
> floor(-1.5)
-2
> parseint("FF", 16)
255
> signum(-13)
-1
> pow(2, 30)
1073741824
String functions
| Function | Purpose |
|---|---|
|
Remove trailing newline characters. |
|
Boolean tests on a string: suffix, prefix, substring. |
|
|
|
Add a number of spaces to the start of every line except the first. |
|
Concatenate a list into a string with a separator, or divide a string at a separator. |
|
Change the case of letters; |
|
Regular expression matching, see Regular expressions. |
|
Replace each occurrence of a substring. A substring wrapped in forward slashes is treated as a regular expression. |
|
Reverse the characters of a string. |
|
Extract a substring by offset and maximum length, counted in Unicode characters. A negative offset counts from the
end, and a length of |
|
Render a template held in a named string, see templatefile and templatestring. |
|
Remove a set of characters, a prefix, a suffix or whitespace from the ends of a string. |
format is the workhorse. Its specification supports the verbs %s, %d, %f, %e, %g, %t, %q, %x, %X,
%b, %o and %%, a width and a precision, flags such as 0 (pad with zeros) and - (left-align), an explicit argument position such as %[1]s, and %#v, which prints any value as JSON and is handy in
validation error messages. For a single substitution, an interpolation sequence in a template is usually more readable
than format("Hello, %s!", var.name); reach for format when you need padding, precision or several arguments.
variable "environment" {
type = string
default = "dev"
}
locals {
prefix = format("bookshelf-%s", var.environment)
# Zero-padded ordinal: bookshelf-dev-catalog-api-01
first_catalog_name = format("%s-catalog-api-%02d", local.prefix, 1)
# One DNS name per service.
dns_names = formatlist("%s.bookshelf.example.com", ["catalog", "reviews", "www"])
# Normalize a Terraform identifier into a DNS label.
dns_label = replace("catalog_api", "_", "-")
# Strip a leading v from a version tag.
image_version = trimprefix("v1.4.2", "v")
# Parse the major version out of the tag.
major_version = split(".", trimprefix("v1.4.2", "v"))[0]
}
Source: Terraform Docs — format function, Terraform Docs — formatlist function and Terraform Docs — replace function.
> format("%s-catalog-api-%02d", "bookshelf-dev", 1)
"bookshelf-dev-catalog-api-01"
> join(", ", formatlist("%s.bookshelf.example.com", ["catalog", "reviews", "www"]))
"catalog.bookshelf.example.com, reviews.bookshelf.example.com, www.bookshelf.example.com"
> replace("catalog_api", "_", "-")
"catalog-api"
> trimprefix("v1.4.2", "v")
"1.4.2"
> substr("bookshelf-prod", 0, 9)
"bookshelf"
> startswith("reviews-api", "reviews")
true
> join("-", ["bookshelf", "dev", "web"])
"bookshelf-dev-web"
Two details cause most string-function bugs. replace with a plain substring replaces every occurrence, not only the
first. And format with a list argument does not iterate: format("%s", ["a", "b"]) is an error, while formatlist
produces one string per element and accepts scalars that it repeats for every element.
Collection functions
| Function | Purpose |
|---|---|
|
Whether all, or any, of the elements of a list are true. |
|
Split a list into fixed-size chunks. |
|
First argument that is not null or an empty string; first list argument that is not empty. |
|
Remove null and empty-string elements from a list of strings. |
|
Combine two or more lists into one. |
|
Whether a list, tuple or set contains a value. Since Terraform 1.16 it can test for |
|
Remove duplicates from a list, keeping the first occurrence. |
|
One element of a list, with wrap-around for an index beyond the end and, since Terraform 1.10, negative indices. |
|
Replace nested lists by their elements, recursively. |
|
Position of the first element equal to a value. |
|
The keys, or the values, of a map or object, in lexical order of the keys. |
|
Number of elements of a collection, or characters of a string. |
|
Value for a key of a map, or a default when the key is absent. |
|
Subset of one list chosen by matching the elements of another. |
|
Combine maps or objects; the later argument wins on a duplicate key. Since 1.16 it accepts |
|
The only element of a list, set or tuple of zero or one elements; |
|
A list of numbers from a start to a limit by a step. |
|
The same sequence in reverse order. |
|
Set operations; |
|
Consecutive elements of a list, by start and end index. |
|
Strings in lexicographic order. |
|
Sum of a list or set of numbers. |
|
Turn a map of lists into a map of keys by element. |
|
Build a map from a list of keys and a list of values. |
The documentation still lists list and map as deprecated since Terraform 0.12. Use tolist and tomap (or the
literal syntax [...] and {...}) instead; the OpenTofu function listing has no list or map at all.
Collections in the language are immutable values, so every function here returns a new collection. The ones that matter
most for infrastructure code are merge (tags), lookup and coalesce (defaults), flatten and setproduct
(reshaping data into the single flat collection that for_each needs) and zipmap (pairing names with computed
values).
locals {
services = {
catalog-api = { port = 8080, replicas = 2 }
reviews-api = { port = 8081, replicas = 1 }
web = { port = 80, replicas = 3 }
}
common_tags = {
project = "bookshelf"
environment = var.environment
managed_by = "terraform"
}
# Later arguments win: the service tag is added, and a team could override environment here.
catalog_tags = merge(local.common_tags, { service = "catalog-api" })
# keys() and values() order follow the sorted keys: catalog-api, reviews-api, web.
service_names = keys(local.services)
total_replicas = sum([for svc in values(local.services) : svc.replicas])
# One element per container, flattened to a single list that for_each can consume as a set.
container_names = flatten([
for name, svc in local.services : [
for n in range(svc.replicas) : format("%s-%02d", name, n + 1)
]
])
# A default for a service without a custom port.
cache_port = lookup({ for name, svc in local.services : name => svc.port }, "redis", 6379)
# Every service in every environment: four combinations, order preserved.
fqdns = [
for pair in setproduct(["dev", "prod"], ["catalog", "reviews"]) :
"${pair[1]}.${pair[0]}.bookshelf.example.com"
]
}
Source: Terraform Docs — merge function, Terraform Docs — flatten function and Terraform Docs — setproduct function.
The snippet assumes the environment variable of the previous example. With that configuration in the working
directory, terraform console evaluates the locals:
> local.total_replicas
6
> join(", ", local.service_names)
"catalog-api, reviews-api, web"
> local.container_names
[
"catalog-api-01",
"catalog-api-02",
"reviews-api-01",
"web-01",
"web-02",
"web-03",
]
> local.cache_port
6379
> local.fqdns
[
"catalog.dev.bookshelf.example.com",
"reviews.dev.bookshelf.example.com",
"catalog.prod.bookshelf.example.com",
"reviews.prod.bookshelf.example.com",
]
A few more collection results, taken from the function pages:
> element(["a", "b", "c"], 3)
"a"
> element(["a", "b", "c"], -1)
"c"
> zipmap(["a", "b"], [1, 2])
{
"a" = 1
"b" = 2
}
> coalesce("", "b")
"b"
> merge({a="b", c="d"}, {e="f", c="z"})
{
"a" = "b"
"c" = "z"
"e" = "f"
}
> flatten([["a", "b"], [], ["c"]])
[
"a",
"b",
"c",
]
Source: Terraform Docs — element function, Terraform Docs — zipmap function and Terraform Docs — coalesce function.
Four cautions apply to this category:
-
lookup(map, key, default)is documented with a required default; the two-argument form is deprecated because it is equivalent to the index syntaxmap[key]. -
elementis only needed for its wrap-around behavior; uselist[index]otherwise. -
coalescetreats an empty string as missing, and all its arguments must convert to one type:coalesce({}, "hello")is an error. -
oneis the idiomatic way to read a value from a resource created withcount = condition ? 1 : 0, for exampleone(terraform_data.redis[*].output), which is the single value ornull.
Encoding functions
| Function | Purpose |
|---|---|
|
Standard Base64 of the UTF-8 bytes of a string, and back. |
|
Compress a string with gzip and encode it as Base64. |
|
Parse CSV text with a header row into a list of maps. |
|
Convert a value to a JSON string, or parse JSON into a value. |
|
Convert text to and from Base64 in another character encoding, such as UTF-16LE. |
|
Percent-encode a string for use in a URL query. |
|
Convert a value to a YAML 1.2 string, or parse YAML into a value. |
jsonencode and yamlencode are the functions to prefer whenever the goal is a JSON or YAML document: they handle
quoting, escaping and delimiters, they take ordinary Terraform values, and, for JSON, Terraform can show a structural
diff in the plan, whereas it treats block-style YAML as one multi-line string. The documentation recommends JSON even
when the remote system also accepts YAML, unless humans will read or edit the result. Neither round-trips exactly,
because the target formats cannot represent every Terraform type.
locals {
# An IAM-style policy for the covers bucket. Encoding a value avoids hand-written braces and quotes.
covers_read_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["s3:GetObject"]
Resource = "arn:aws:s3:::bookshelf-covers-example/*"
}]
})
# A Kubernetes-style ConfigMap payload for catalog-api, as YAML for people to read.
catalog_settings_yaml = yamlencode({
server = { port = 8080 }
database = { host = "postgres.bookshelf.internal", name = "catalog" }
})
# A small CSV roster decoded into a list of objects.
teams = csvdecode("name,owner\ncatalog,alice\nreviews,bob")
}
Source: Terraform Docs — jsonencode function, Terraform Docs — yamlencode function and Terraform Docs — csvdecode function.
> jsonencode({name = "catalog-api", port = 8080})
"{\"name\":\"catalog-api\",\"port\":8080}"
> jsondecode("{\"replicas\": 2}").replicas
2
> base64encode("catalog")
"Y2F0YWxvZw=="
> base64decode("Y2F0YWxvZw==")
"catalog"
> urlencode("Hello World!")
"Hello+World%21"
> textencodebase64("Hello World", "UTF-16LE")
"SABlAGwAbABvACAAVwBvAHIAbABkAA=="
Source: Terraform Docs — base64encode function, Terraform Docs — urlencode function and Terraform Docs — textencodebase64 function.
base64gzip is used to fit a larger document, such as a cloud-init payload, into an argument with a size limit.
OpenTofu adds the inverse, base64gunzip, and urldecode (both since OpenTofu 1.7); Terraform has neither. In
OpenTofu 1.13 the exact bytes that base64gzip produces changed to a different but equivalent encoding, so a resource
argument that stores its result can show a diff after the upgrade.
Filesystem functions
| Function | Purpose |
|---|---|
|
Convert a path to an absolute path. |
|
The last element of a path, or the path without its last element. |
|
Content of a file as a UTF-8 string. |
|
Content of a file as Base64, which is the way to read binary files. |
|
Whether a regular file exists at a path. |
|
The set of file names matching a glob pattern below a path. |
|
Replace a leading |
|
Render a template file with variables, see templatefile and templatestring. |
Three symbols belong with these functions because they supply the path argument: path.module (the directory of the
module whose expression is being evaluated), path.root (the directory of the root module) and path.cwd (the
directory in which Terraform was started). Almost every file function should start from path.module, so that the
module keeps working when it is called from somewhere else.
The most important rule is that the file functions read the disk while Terraform loads and plans the configuration.
Functions do not take part in the dependency graph, so they can only read files that already exist when the run starts.
A file generated during the run, for example by a local_file resource, cannot be read with file; the local_file
data source reads a file while respecting resource dependencies, but generating files and reading them back in the same
run is a design smell. Content that is not valid UTF-8 makes file fail, so use filebase64 for binary content.
fileset returns a set of paths relative to its first argument, always with forward slashes. The patterns are *
(any characters but a separator), ** (any characters including separators, only as a whole path component),
?, {a,b} alternatives and [class] character classes. A common use is one resource per matched file:
terraform {
required_version = ">= 1.15"
}
# Expected layout next to this file:
# nginx/web.conf
# nginx/catalog.conf
# covers/seed/cover-001.jpg (binary files)
locals {
nginx_files = fileset(path.module, "nginx/*.conf")
cover_seeds = fileset("${path.module}/covers/seed", "*.jpg")
}
resource "terraform_data" "nginx_config" {
for_each = local.nginx_files
# Changing a file changes its hash and therefore replaces the resource.
triggers_replace = filesha256("${path.module}/${each.value}")
}
output "nginx_file_count" {
value = length(local.nginx_files)
}
Source: Terraform Docs — fileset function, Terraform Docs — file function and Terraform Docs — terraform_data.
Terraform 1.13 started to check that the filesystem functions return the same result during apply as they did during plan, to catch a file that changed in between, and Terraform 1.14 relaxed the check for filesystem functions evaluated in provider configuration, where a different result is acceptable.
templatefile and templatestring
Both functions render the template language that is also used inside quoted strings and heredocs: interpolation
sequences such as ${name} and directives such as %{ for item in items }...%{ endfor } and %{ if cond }...%{ endif }.
The template language itself is described in
Types and Expressions; here is how the two functions deliver a
template.
templatefile(path, vars) reads a file and renders it with the variables of the vars object. Each key of the object
becomes a variable inside the template; names must start with a letter and contain only letters, digits and
underscores. The template may call any function except templatefile itself (recursion is not allowed). The
recommended file extension is .tftpl: Terraform does not require it, but editors use it to pick better
highlighting. Like file, templatefile reads UTF-8 text, can only read files that exist when the run starts, and
returns an error for invalid UTF-8.
Create a template for the part of the web reverse-proxy configuration that lists the catalog backends:
mkdir -p templates
cat > templates/catalog-upstream.conf.tftpl <<'EOF'
%{ for addr in catalog_addrs ~}
server ${addr}:${port};
%{ endfor ~}
EOF
Render it:
locals {
catalog_upstream = templatefile("${path.module}/templates/catalog-upstream.conf.tftpl", {
catalog_addrs = ["10.0.1.10", "10.0.1.11"]
port = 8080
})
}
The value of local.catalog_upstream is this text, one line for each address, each ending with a newline:
server 10.0.1.10:8080;
server 10.0.1.11:8080;
The ~ markers strip the whitespace around the directives, including the indentation of the following line. That is
why the body has no leading spaces: an indented server line would lose its indentation. Use ~ on the side where you
want the whitespace removed and expect the final newline of the template to remain.
templatestring(template, vars) was added in Terraform 1.9. It renders a template that is not in a file but in a
string, typically one fetched from a remote location or built in a local value, and its first argument must be a
reference to an object defined in the module, such as a local value or a data source attribute. The documentation
states that the template expression cannot be given directly as the first argument; that is deliberate, because a
template literal in the call would simply be a normal string template. The second argument is the object of
variables.
locals {
# $$ keeps the sequence literal, so it is rendered later by templatestring and not now.
banner_template = "Welcome to $${shop}, running in $${env}"
banner = templatestring(local.banner_template, {
shop = "Bookshelf"
env = var.environment
})
}
> local.banner
"Welcome to Bookshelf, running in dev"
Building a template dynamically in a local value and then rendering it is a last resort: the result is hard to
understand, it is sensitive to unexpected input and, because built-in functions can read the local filesystem, an
untrusted template could include data from files on the machine running Terraform. Prefer templatefile for
templates stored in your own repository.
The legacy template_file data source
Tutorials written for Terraform 0.11 and earlier render templates with the template_file data source of the
hashicorp/template provider, passing a template string (usually the result of file) and a vars map and then reading the rendered attribute. Since Terraform 0.12 the built-in templatefile function does the same without a
provider, and the provider’s own documentation says to use the function instead unless you are on 0.11 or older. The
provider repository is archived, no new releases are published, and its builds do not cover newer platforms, so
configurations that still use it should be migrated: replace the data source by a templatefile call, move vars
into the second argument and read the result directly instead of through .rendered.
# Before (legacy, do not copy):
# data "template_file" "init" {
# template = file("${path.module}/templates/init.tpl")
# vars = { catalog_url = "http://catalog-api:8080" }
# }
# user_data = data.template_file.init.rendered
# After:
locals {
init_script = templatefile("${path.module}/templates/init.tftpl", {
catalog_url = "http://catalog-api:8080"
})
}
When not to template
A template is a tool for text whose shape is mostly fixed: a shell script, an nginx or HAProxy configuration, a cloud-init document, an email body. It is the wrong tool for structured data.
-
JSON and YAML. Do not write braces, quotes, commas and indentation in a template and fill holes in them. Build the value with ordinary expressions and encode it with
jsonencodeoryamlencode. A template file can still be used as a thin wrapper: a template whose entire content is a single call such as${jsonencode({ backends = [for addr in ip_addrs : "${addr}:${port}"] })}produces valid JSON without any escaping, though the same call inline in a local value avoids the file. -
Logic. A template with nested
%{ for }and%{ if }directives is a program in a poor language with no tests. When there is real branching, compute the data in Terraform, pass the final lists and strings to the template and keep the template to simple loops and substitutions. -
Secrets. A rendered template is a plain string value. If a secret flows into
vars, the rendered text becomes sensitive and, if it is passed to a resource argument, ends up in the state. Prefer to fetch secrets at run time from the machine (a secret manager call in the user-data script) instead of baking them into rendered files. -
Generated files. Neither function can read a file produced during the run.
locals {
# Prefer this: the data is a value, and the encoder takes care of escaping and delimiters.
catalog_config_json = jsonencode({
listen = ":8080"
upstream = [for addr in ["10.0.1.10", "10.0.1.11"] : "${addr}:8080"]
})
}
Date and time functions
| Function | Purpose |
|---|---|
|
Convert an RFC 3339 timestamp to another text format. |
|
The time at which the plan was made, as an RFC 3339 string (Terraform 1.5 and later). |
|
Add a duration such as |
|
Compare two timestamps: |
|
The current UTC time, as an RFC 3339 string. |
Timestamps in the language are strings in RFC 3339 form, such as 2026-10-23T09:30:00Z. formatdate turns one into a
machine-oriented format: YYYY, MM, DD, hh, mm, ss, EEE and ZZZ among others. It is not locale-aware and
produces English month and day names only. timeadd uses the duration units ns, us, ms, s, m and h; there is
no unit for days, so thirty days is 720h. timecmp takes the UTC offsets of its arguments into account.
> formatdate("YYYY-MM-DD", "2026-10-23T09:30:00Z")
"2026-10-23"
> timeadd("2026-10-23T09:30:00Z", "720h")
"2026-11-22T09:30:00Z"
> timecmp("2026-10-23T09:30:00Z", "2026-11-22T09:30:00Z")
-1
Source: Terraform Docs — formatdate function, Terraform Docs — timeadd function and Terraform Docs — timecmp function.
timestamp versus plantimestamp
The two current-time functions differ in when they are evaluated, and that decides where each is usable.
-
timestamp()changes every second. Terraform cannot predict it during planning, so the value is unknown in the plan and is taken when the plan is applied. Used directly in a resource argument it makes every run show a diff, so the documentation recommends against it there; the exceptions are alifecycleignore_changesto keep the first value, and a condition evaluated at apply time. -
plantimestamp()returns the time of the plan and is fixed for the whole plan operation, so it can be used in places that need a known value during planning and validation, which makes it the right tool for time-based checks. It is not available interraform console, and the value changes on every new plan. During a refresh-only plan it may be recalculated but is not passed to resources, so compare it only against timestamps that providers export, not against timestamps your own configuration generates.
A check block that warns before the Bookshelf certificate expires uses plantimestamp, because a check runs at the end of
every plan and apply and a failing check is a warning that does not stop the run:
terraform {
required_version = ">= 1.15"
required_providers {
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
}
}
check "bookshelf_certificate" {
data "tls_certificate" "web" {
url = "https://www.bookshelf.example.com"
}
assert {
# Fail the check when less than 30 days (720h) of validity remain.
condition = timecmp(plantimestamp(), timeadd(data.tls_certificate.web.certificates[0].not_after, "-720h")) < 0
error_message = "The www.bookshelf.example.com certificate expires in less than 30 days."
}
}
The check block itself, the scoped data source and what a failed check does to the run are explained in
Lifecycle, Validation and Checks.
When the goal is a value that is recorded once and then stays stable, such as the creation date of a resource, use
the time_static resource of the hashicorp/time provider instead of any function: it stores the time in the state
and does not change on later runs.
terraform {
required_version = ">= 1.15"
required_providers {
time = {
source = "hashicorp/time"
version = "~> 0.14"
}
}
}
resource "time_static" "catalog_created" {}
output "catalog_created" {
# Stable across runs, unlike timestamp().
value = formatdate("YYYY-MM-DD", time_static.catalog_created.rfc3339)
}
Hash and crypto functions
| Function | Purpose |
|---|---|
|
Hexadecimal digest of a string. |
|
Digest encoded as Base64 instead of hexadecimal. This is not the same as encoding the hexadecimal digest. |
|
The same digests computed over the contents of a file, including binary files. |
|
Blowfish-based password hash in the Modular Crypt Format, with an optional cost that defaults to 10. |
|
Decrypt Base64 ciphertext with a PEM-encoded RSA private key. |
|
A random UUID-format string, or a name-based UUID for a namespace and a name. |
The file variants exist because file accepts only UTF-8 text: filebase64sha256 hashes arbitrary bytes. Hashes are
used as change detectors (a resource is replaced when the hash of a source file changes), as stable identifiers
derived from content, and for integrity values that APIs expect, such as the base64 SHA-256 of a function package.
locals {
# Deterministic: the same input always gives the same fingerprint.
nginx_config_hash = filesha256("${path.module}/nginx/web.conf")
# A short, stable suffix derived from a name, for globally unique bucket names.
covers_bucket_suffix = substr(sha256("bookshelf-covers-${var.environment}"), 0, 8)
}
resource "terraform_data" "web_config" {
# Replace when the configuration file content changes.
triggers_replace = local.nginx_config_hash
}
Source: Terraform Docs — filesha256 function, Terraform Docs — sha256 function and Terraform Docs — terraform_data.
> base64sha256("hello world")
"uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek="
Three warnings are worth keeping in mind. md5 and sha1 are fine as change detectors but are not secure hashes,
so use sha256 or sha512 when integrity matters. bcrypt includes a random salt, so every call returns a different
string for the same input and using it in a resource argument creates a diff on every plan; the documentation
recommends it only in provisioner blocks or in data sources whose results feed provisioners. And uuid is also random
on each call: it produces a spurious diff in resource arguments and is rarely the right choice, because the random
provider keeps the generated value in the state (random_id offers equivalent randomness). Use uuidv5 when you need
a stable identifier derived from a name.
IP network functions
| Function | Purpose |
|---|---|
|
The IP address of one host in a prefix, by host number. |
|
Convert an IPv4 prefix to a netmask. |
|
One subnet inside a prefix, chosen by the number of extra bits and a network number. |
|
Several consecutive subnets inside a prefix, sized by a list of extra bits and numbered automatically. |
|
OpenTofu only (since 1.7): whether an address or prefix is within a prefix. |
cidrsubnet(prefix, newbits, netnum) extends the prefix length of prefix by newbits and fills the new bits with
the binary value of netnum. For cidrsubnet("10.1.2.0/24", 4, 15) the new prefix is /28 and the four new bits are
1111, giving 10.1.2.240/28. Both IPv4 and IPv6 prefixes are accepted and the result uses the same family.
cidrsubnets(prefix, newbits...) allocates a sequence of consecutive ranges with possibly different sizes and numbers
them automatically, starting with zero. This makes it the better tool for carving up a network plan in one expression.
The documentation gives a rule to follow: once addresses from the result are assigned to real infrastructure, do not change any
of the existing newbits arguments, because that would move every later range; appending new arguments is safe as long as address
space remains.
locals {
bookshelf_cidr = "10.20.0.0/16"
# Four /24 networks, numbered in order. Never reorder or edit these entries once deployed.
subnet_names = ["public-a", "public-b", "private-a", "private-b"]
subnet_cidrs = zipmap(local.subnet_names, cidrsubnets(local.bookshelf_cidr, 8, 8, 8, 8))
# A specific /24 chosen by network number.
database_cidr = cidrsubnet(local.bookshelf_cidr, 8, 10)
# The fifth usable address of the database subnet, e.g. for a static private IP.
database_ip = cidrhost(local.database_cidr, 5)
}
> local.subnet_cidrs
{
"private-a" = "10.20.2.0/24"
"private-b" = "10.20.3.0/24"
"public-a" = "10.20.0.0/24"
"public-b" = "10.20.1.0/24"
}
> local.database_cidr
"10.20.10.0/24"
> local.database_ip
"10.20.10.5"
> cidrnetmask("10.20.0.0/16")
"255.255.0.0"
> join(" ", cidrsubnets("10.1.0.0/16", 4, 4, 8, 4))
"10.1.0.0/20 10.1.16.0/20 10.1.32.0/24 10.1.48.0/20"
The functions interpret IPv4 octets with leading zeros as decimal numbers, which is a historical accident that
differs from systems that read them as octal; the documentation recommends not relying on it. For named, skippable
allocations the hashicorp/subnets/cidr module wraps cidrsubnets with symbolic names.
Type conversion functions
| Function | Purpose |
|---|---|
|
Convert to a primitive type. |
|
Convert to a list, set or map. |
|
Convert to any type constraint, including |
|
Catch an error in an expression, see try and can. |
|
Mark handling, see Functions for sensitive and ephemeral values. |
|
Show the type of a value. Available only in |
Terraform converts values automatically where a type is expected, so explicit conversion is rarely required. It helps
when the intent should be visible (a string read from a file used as a number), when a collection must become a set
(to deduplicate it for for_each) and when a result has an ambiguous type.
convert(value, type_constraint), introduced in Terraform 1.15 and OpenTofu 1.13, evaluates the value and converts it
to the type constraint written in the second argument, using the same syntax as the type argument of a variable,
including optional() attributes with defaults. The older conversion functions remain for compatibility and each
behaves as the matching convert call; for collections the documentation prefers convert with a precise element type
because tolist and its siblings rely on the less precise any element type. OpenTofu additionally requires the
defaults of optional attributes in the constraint to be constant values.
terraform {
required_version = ">= 1.15"
}
locals {
# A string read from configuration, used as a number.
port = tonumber("8080")
# Deduplicate environments before a for_each.
environments = toset(["dev", "prod", "dev"])
# Fill in defaults for optional attributes, with a precise type.
services = convert(
{
catalog-api = {}
web = { replicas = 3 }
},
map(object({ replicas = optional(number, 2) }))
)
}
> local.environments
toset([
"dev",
"prod",
])
> local.services
tomap({
"catalog-api" = {
"replicas" = 2
}
"web" = {
"replicas" = 3
}
})
try and can
try(expression, ...) evaluates its arguments in order and returns the first one that does not produce an error. If
all fail, the error lists every problem it met. can(expression) evaluates one expression and returns true when it
produced a result and false when it failed. Both catch only dynamic errors, errors caused by data that is known only
at run time (a missing attribute, a failed conversion). They do not catch constructs that are invalid for every input,
such as a malformed reference or a reference to an undeclared local value.
Use try to normalize data of uncertain shape, and keep it confined to a few local values, so the rest of the module
can use plain references. Use can in variable validation to turn an error into a boolean; the documentation
recommends it only for simple tests in validation rules and recommends try elsewhere.
variable "catalog_settings" {
type = any
description = "Settings decoded from a YAML file; some attributes may be missing."
default = { replicas = 2 }
}
variable "catalog_hosts" {
type = any
default = "catalog-api"
}
variable "service_name" {
type = string
default = "catalog-api"
validation {
# regex errors when the pattern does not match; can turns that into false.
condition = can(regex("^[a-z][a-z0-9-]{2,30}$", var.service_name))
error_message = "The service name must be lowercase letters, digits and hyphens, 3 to 31 characters, starting with a letter."
}
}
locals {
# The only place that deals with missing attributes.
catalog = {
replicas = try(var.catalog_settings.replicas, 1)
port = try(var.catalog_settings.port, 8080)
tags = try(var.catalog_settings.tags, {})
}
# A value that may arrive as a single string or as a list of strings.
hosts = try(
[tostring(var.catalog_hosts)],
tolist(var.catalog_hosts),
)
}
Overusing either function hides real mistakes and produces a configuration nobody can reason about, which is why the
documentation warns against suppressing errors with try beyond attribute and type checks. A try around a resource
attribute that should always exist turns a clear plan error into a silent default.
Regular expressions
regex(pattern, string) and regexall(pattern, string) apply a regular expression to a string. The pattern syntax is
that of the Go regular expression engine, as listed in the documentation (character classes, repetition, named groups
written (?P<name>x), Unicode classes and zero-width assertions such as ^, $ and \b). Because a pattern is
written as a quoted string, every backslash that belongs to the pattern must be doubled: \\d, not \d. A heredoc
or the replace form with slashes avoids some of the noise.
The return type of regex depends on the capture groups in the pattern:
| Capture groups | Result of regex |
|---|---|
none |
a single string with the whole match |
one or more unnamed groups |
a list of the captured substrings, in order |
one or more named groups |
a map from group name to captured substring |
Named and unnamed groups cannot be mixed in one pattern. regex raises an error when the pattern does not match at all,
which is useful to fail fast but bad for testing; regexall returns a list with one element per match (an empty list
when there is none), so length(regexall(...)) > 0 is the way to test for a match, and it has the same element types
as regex. The replace function also accepts a pattern, when the search string is wrapped in forward slashes, and the
replacement can use $1 or $name to insert capture groups.
locals {
image = "registry.bookshelf.example.com/catalog-api:1.4.2"
# Named groups give a map.
parts = regex("^(?P<repo>[^:]+):(?P<tag>[0-9.]+)$", local.image)
# Test without erroring when there is no match.
is_semver_tag = length(regexall("^[0-9]+\\.[0-9]+\\.[0-9]+$", local.parts.tag)) > 0
# Remove the tag with a pattern between slashes.
image_without_tag = replace(local.image, "/:[0-9.]+$/", "")
}
Source: Terraform Docs — regex function, Terraform Docs — regexall function and Terraform Docs — replace function.
> join(",", regexall("[a-z]+", "1234abcd5678efgh9"))
"abcd,efgh"
> length(regexall("[a-z]+", "123456789")) > 0
false
> regex("[a-z]+", "53453453.345345aaabbbccc23454")
"aaabbbccc"
The documentation’s own advice is to prefer a more specialized function when one exists: parse JSON with jsondecode,
YAML with yamldecode, CIDR blocks with the cidr family of functions, and prefixes and suffixes with startswith,
endswith, trimprefix and trimsuffix. A regular expression is hard to read and obscures intent.
Functions for sensitive and ephemeral values
Terraform tracks two kinds of marks on values. A sensitive mark makes the CLI redact the value in plan and apply
output, but the value is still written to the state and the plan file. An ephemeral mark (Terraform 1.10,
OpenTofu 1.11) keeps the value out of both files and restricts where it can be used. Four functions read or change
these marks.
The full model, including sensitive = true on variables and outputs, ephemeral resources and write-only arguments, is
in Sensitive Data, Ephemeral Values and Write-Only Arguments.
| Function | Available since | Behavior |
|---|---|---|
|
Terraform 0.15 |
Returns a copy of any value marked sensitive. Prefer |
|
Terraform 0.15 |
Returns the value without the sensitive mark. It is an error to call it on a value that is not sensitive, because the call would be redundant. It exposes the value in output, so use it sparingly. |
|
Terraform 1.8, OpenTofu 1.7 |
Returns |
|
Terraform 1.10 |
Returns the value with every ephemeral part replaced by |
A value is sensitive when it is declared so or derived from a sensitive value. Terraform propagates the mark
conservatively, which sometimes marks more than necessary: a SHA-256 hash of a secret, or the one non-secret field of a
decoded JSON document, is still sensitive. nonsensitive is the escape hatch for the cases where you know, from the
properties of your system and its threat model, that the derived value is safe. It is your responsibility that the
argument removes everything sensitive; if a secret appears in output because of an inappropriate call, that is a bug in the module.
Leave a comment next to every use that says why it is safe.
terraform {
required_version = ">= 1.15"
}
variable "database_password" {
type = string
description = "Password of the PostgreSQL superuser. Supply it with TF_VAR_database_password."
sensitive = true
}
variable "registry_credentials_json" {
type = string
description = "JSON with a user name and a password for the private registry. Supply it with TF_VAR_registry_credentials_json."
sensitive = true
}
locals {
# Derived from a sensitive variable, so the whole decoded object is sensitive.
registry = jsondecode(var.registry_credentials_json)
# The user name is not secret in this setup; the password stays sensitive.
# SAFE: only the user name field is exposed, never the password field.
registry_user = nonsensitive(local.registry["username"])
# A short fingerprint of a high-entropy password lets operators confirm which secret is deployed.
# SAFE: eight hex characters of a SHA-256 digest of a long random password reveal nothing usable.
password_fingerprint = nonsensitive(substr(sha256(var.database_password), 0, 8))
# A secret defined inside the module, read from a file that is kept out of version control.
# (illustrative: the file must exist next to the configuration)
# seed_secret = sensitive(file("${path.module}/secrets/seed.txt"))
}
output "registry_user" {
value = local.registry_user
}
output "database_password_fingerprint" {
value = local.password_fingerprint
}
output "database_password_is_sensitive" {
# Evaluates to true. Reading a mark does not expose the value.
value = issensitive(var.database_password)
}
Source: Terraform Docs — nonsensitive function, Terraform Docs — sensitive function and Terraform Docs — issensitive function.
> var.database_password
(sensitive value)
> issensitive(var.database_password)
true
> issensitive("hello")
false
> nonsensitive("clear")
Error: Invalid function argument
Invalid value for "value" parameter: the given value is not sensitive, so this
call is redundant.
ephemeralasnull bridges ephemeral values to places that do not accept them. An ordinary output and a resource argument
that is not write-only reject an ephemeral value, because both end up in the state or the plan. The function replaces the
ephemeral parts by null, so the non-ephemeral rest can be used and the presence of a field can still be shown.
terraform {
required_version = ">= 1.15"
}
variable "environment" {
type = string
default = "dev"
}
variable "session_token" {
type = string
description = "Short-lived token for the deployment. Supply it with TF_VAR_session_token."
ephemeral = true
default = null
}
output "deployment_summary" {
# environment stays; session_token is replaced by null, so the output can be recorded in the state.
value = ephemeralasnull({
environment = var.environment
session_token = var.session_token
})
}
Provider-defined functions and the built-in terraform provider
Since Terraform 1.8 (OpenTofu 1.7), providers can expose functions. A provider function is called as
provider::<local_name>::<function_name>(...), where <local_name> is the key the provider has in
required_providers; with a provider alias the form is provider::<local_name>::<alias>::<function_name>(...).
The functions of external providers are documented in the Terraform Registry, in each provider’s own documentation, and
the only requirement from the module is the usual required_providers entry. Writing a function for a provider is a
provider development task. See Providers for how providers are installed and
configured.
The terraform provider that is built into the Terraform core (source address terraform.io/builtin/terraform,
unrelated to the old external hashicorp/terraform provider) offers three functions, since Terraform 1.8 and
OpenTofu 1.10. They are provider functions, so the prefix is provider::terraform::, not a bare terraform::
namespace, and the provider must be declared in required_providers for them to be callable.
| Function | Purpose |
|---|---|
|
Takes an object and returns a string in |
|
Takes the content of a |
|
Takes any value and returns a string with Terraform expression syntax approximating it. Its primary use is the
|
The documentation calls all three rarely needed and recommends exchanging data between configurations with data
sources, not generated .tfvars files, and using jsonencode or yamlencode for general serialization because the exact
syntax produced for some values may change in future versions.
terraform {
required_version = ">= 1.15"
required_providers {
terraform = {
source = "terraform.io/builtin/terraform"
}
}
}
locals {
workspace_vars = {
environment = "dev"
zones = ["a", "b"]
}
decoded = provider::terraform::decode_tfvars(<<-EOT
environment = "dev"
EOT
)
}
output "decoded_environment" {
value = local.decoded.environment
}
output "tfvars_preview" {
# A string in .tfvars syntax. Inspect it; do not parse it by hand.
value = provider::terraform::encode_tfvars({ environment = "dev" })
}
output "zones_expression" {
value = provider::terraform::encode_expr(local.workspace_vars.zones)
}
Source: Terraform Docs — provider::terraform::encode_tfvars, Terraform Docs — provider::terraform::decode_tfvars and Terraform Docs — provider::terraform::encode_expr.
The documentation for the same family also lists a symbol, terraform.applying, which is not a function but is
referred to as one of the built-in terraform values (Terraform 1.10). It is true while Terraform runs an apply
operation, including in destroy mode, and false during every other operation. Since it is an ephemeral value, it
is only valid in ephemeral contexts: write-only arguments, ephemeral variables, local values, ephemeral resources,
ephemeral outputs, provider configuration, and provisioner and connection configuration. It is not written to plan or
state files. The standard use is to give a plan read-only credentials and an apply write credentials:
terraform {
required_version = ">= 1.15"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
variable "read_role_arn" {
type = string
description = "Role used by plan. Supply it with TF_VAR_read_role_arn."
}
variable "write_role_arn" {
type = string
description = "Role used by apply. Supply it with TF_VAR_write_role_arn."
}
locals {
role_arn = terraform.applying ? var.write_role_arn : var.read_role_arn
}
provider "aws" {
region = "eu-west-1"
assume_role {
role_arn = local.role_arn
}
}
OpenTofu documents the same symbol as terraform.applying and tofu.applying, and its built-in provider page documents
encode_tfvars and decode_tfvars plus the same expression encoder (see Terraform and OpenTofu differences).
Pure and impure functions
The Terraform documentation does not label functions "pure" or "impure", but the distinction explains how functions behave in the plan and the apply, and the Terraform changelog itself speaks of impure functions. A working classification:
| Kind | Examples and behavior |
|---|---|
Pure |
The result depends only on the arguments: |
Impure: random or clock |
|
Reads the world |
|
Fixed per plan |
|
Practical rules follow from this:
-
Do not put an impure function in a resource argument that is compared on every plan. Move the value into the state once:
time_staticfor a time,random_idorrandom_passwordfor random data. -
Keep file-reading functions pointed at committed, stable files, starting from
path.module. -
Do not use an impure function as the source of a
countorfor_eachvalue, because those must be known when the plan is made. -
To record a value at creation and then ignore it, combine the function with
lifecycle { ignore_changes = [...] }; this is the documented exception fortimestampanduuid.
The consequences of unknown values for count and for_each, and the workarounds, are in
Meta-Arguments: count, for_each and depends_on.
Experimenting with terraform console
terraform console opens an interactive prompt to evaluate expressions against the current configuration and state.
It is the fastest way to learn a function, to test a regular expression, to see the exact type of a value and to
debug a local that does not do what you expect. Close it with exit, Control-C or Control-D. The documentation’s
examples for each function are console transcripts.
# Interactive session in the directory that contains the configuration
terraform console
# Non-interactive: pipe newline-separated expressions; only the output of the last one is printed
echo 'split(",", "catalog-api,reviews-api,web")' | terraform console
# Evaluate against a plan, so resource values that are not yet created are available
terraform console -plan
# Evaluate in the scope of a child module (Terraform 1.16 and later)
terraform console -scope='module.network'
terraform console -scope='module.network[0]'
terraform console -scope='module.network["key"]'
$ echo 'split(",", "catalog-api,reviews-api,web")' | terraform console
tolist([
"catalog-api",
"reviews-api",
"web",
])
Source: Terraform Docs — terraform console.
The points to remember:
-
Scope. By default expressions are evaluated in the root module, so variables, locals and resources of child modules are not visible. Since Terraform 1.16 the
-scopeoption selects a child module or one of its instances. -
State and lock. The console reads the state of the current workspace from the backend and holds the state lock, so you cannot run another command that modifies the state at the same time. In a project that has never been applied, the state is empty and the console is still good for literals and functions.
-
Planned values. Without
-plan, resource attributes that do not exist yet in the state are shown as unknown, for example(known after apply). With-plan, Terraform makes a plan first, which is slower and, for a configuration that does work during planning (anexternaldata source, for example), can have side effects. -
Functions that behave differently.
typeexists only in the console and is meant for examining values, not for use in expressions;plantimestampis not available in it;timestampreturns the current time at the moment you type it. -
Sensitive output. Sensitive values print as
(sensitive value). Do not paste real secrets into a console session: the session may be logged by a terminal recorder or shell history. -
OpenTofu. The command is
tofu consoleand works the same way.
Terraform and OpenTofu differences
The library is almost identical. The differences are the functions each tool added on its own and the version in which a shared function arrived.
| Function or feature | Terraform | OpenTofu |
|---|---|---|
Provider-defined functions ( |
1.8 |
1.7 |
|
1.9 |
1.7 |
|
1.8 |
1.7 |
|
1.10 |
Documented with the ephemeral values support (1.11) |
|
1.15 |
1.13 |
|
Not available |
1.7 |
|
Not available |
1.13 |
Built-in provider functions |
1.8, together with |
1.10, documented on the built-in provider page with the expression encoder |
Symbol for the apply phase |
|
|
Console scope option |
|
The OpenTofu console page does not list |
Source: OpenTofu Docs — Functions, OpenTofu Docs — Built-in provider, OpenTofu Docs — assume functions and Terraform Docs — Built-in functions.
The assume family of functions is OpenTofu-specific. It lets a module author state facts about a value that will be known only
at apply time, for example that an identifier starts with vpc- or is not null, so OpenTofu can evaluate conditions
during the plan instead of failing during the apply. Whenever possible OpenTofu infers such assumptions automatically.
Because Terraform has no equivalent, a module that must support both tools should not use them.
Because both tools evaluate the same language, the examples on this page work unchanged with tofu, with these
exceptions: the OpenTofu-only functions above, and .tf files that use convert need OpenTofu 1.13 or later. See
OpenTofu for the full comparison of the two tools.
What changed recently
| Version | Change |
|---|---|
Terraform 0.15 |
|
Terraform 1.5 |
|
Terraform 1.8 (2024-04-10) |
Provider-defined functions, the |
Terraform 1.9 (2024-06-26) |
The |
Terraform 1.10 (2024-11-27) |
|
Terraform 1.13 |
The filesystem functions are checked for a consistent result between plan and apply. Terraform 1.14 allowed them to return inconsistent results when evaluated in provider configuration. |
Terraform 1.15 (2026-04-29) |
The |
Terraform 1.16 (2026-08-26) |
|
Terraform 1.17 (2026-10-21) |
|
Source: Terraform CHANGELOG (v1.17), Terraform CHANGELOG (v1.16), Terraform CHANGELOG (v1.10) and OpenTofu CHANGELOG (v1.7).
Deprecations and removals: the list and map functions are marked deprecated since Terraform 0.12 in favor of
tolist and tomap, and omitting the default of lookup is deprecated since 0.7. The template_file data source and
its provider are archived. No built-in function was removed in these releases.
Where the books are outdated or wrong
The two reference books for this section were written for Terraform 1.9 and OpenTofu 1.8 and earlier. For the topics of this page that means:
-
The
template_filedata source, which the books present as the legacy way to render templates, is superseded bytemplatefile(and, for templates held in strings,templatestring). Do not add thetemplateprovider to new code. -
The books predate
convert,ephemeralasnull,terraform.applying, the-scopeoption of the console and the 1.17 error behavior oflogandpow. Where they describe the function list, treat the official reference as the authority for the release you run. -
Examples in older tutorials call
list(...)andmap(...)as functions or use the quoted interpolation form around a bare expression, such as a variable reference wrapped in quotes and dollar-brace; the modern forms aretolist,tomapand the plain expression. -
The books treat templating as a general-purpose way to produce JSON and YAML; the official guidance is to build a value and encode it with
jsonencodeoryamlencode.
On any discrepancy, follow the documentation of the version you run.
Pitfalls
-
Calling
timestamp()oruuid()in a resource argument. The value changes on every run, so every plan shows a diff. Usetime_static,random_idorplantimestampin checks. -
Reading a generated file with
fileortemplatefile. The function runs before the file exists. Make the content a value instead. -
Forgetting that
filesetand the file functions resolve from the current directory unless you start frompath.module. -
Expecting
regexto returnfalsewhen nothing matches; it raises an error. Useregexallwithlengthor wrapregexincan. -
Writing a pattern with single backslashes in a quoted string. The quoted string consumes the first backslash; write
\\d. -
Passing a sensitive value to a function and then being surprised that the output must be marked
sensitive. The mark propagates to every derived value; remove it deliberately withnonsensitive, never casually. -
Using
nonsensitiveon a secret to make an output readable. It also exposes the value in the plan output, the state and the logs of the pipeline. -
Using
tryto hide errors from required attributes. Reserve it for optional or variably-shaped data. -
Changing the existing
newbitsarguments of acidrsubnetscall after subnets exist. Append only. -
Using a function that exists only in one tool in a module that is shared between Terraform and OpenTofu.
-
Using
md5orsha1where an integrity guarantee is needed. They are not secure hashes. -
Using
bcryptin a resource argument. The random salt changes the value on every call.