Fix Terraform "Functions May Not Be Called Here" Error

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
Scope Exact-error fix for Functions may not be called here — variable default restrictions, timestamp example, locals workaround, direct function expressions, dynamic null-default fallback, variable versus local distinction, verified backend restriction, timestamp plan drift, and common mistakes. Does not cover the full Terraform function catalog, try and can tutorials, or general HCL syntax.
Related guides Terraform locals
Terraform variables
Terraform functions
Terraform expressions
known after apply

Terraform prints Functions may not be called here when you place a function call in a configuration argument that must stay static during early processing. The most common trigger is timestamp() inside a variable default.

This article reproduces that error on Terraform 1.15.8, shows where the expression belongs instead, and documents two other verified restrictions. It is not a general Terraform functions catalog.

NOTE
Each scenario uses its own directory under ~/terraform-labs/terraform-functions-may-not-be-called-here/. Failures are in errors/; working fixes are in fixes/ and demos/. Examples use the built-in terraform_data resource — no cloud provider required.

Reproduce "Functions May Not Be Called Here"

The failing configuration puts timestamp() in a variable default:

bash
mkdir -p ~/terraform-labs/terraform-functions-may-not-be-called-here/errors/variable-default-timestamp
cd ~/terraform-labs/terraform-functions-may-not-be-called-here/errors/variable-default-timestamp
bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
}

variable "timestamp" {
  type    = string
  default = timestamp()
}

resource "terraform_data" "example" {
  input = var.timestamp
}
EOF

On Terraform 1.15.8 the configuration must be valid before initialization completes, so the error appears during terraform init rather than a separate validate step:

bash
terraform init -input=false
output
Error: Function calls not allowed

  on main.tf line 7, in variable "timestamp":
   7:   default = timestamp()

Functions may not be called here.

The default argument requires a literal value and cannot reference other configuration objects. Function calls therefore belong in expression-capable contexts such as locals or resource arguments rather than in the variable default.


Move the calculated value to locals

The usual fix is to calculate the value in a locals block and reference that local from resources.

bash
mkdir -p ~/terraform-labs/terraform-functions-may-not-be-called-here/fixes/locals-timestamp
cd ~/terraform-labs/terraform-functions-may-not-be-called-here/fixes/locals-timestamp
bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
}

locals {
  timestamp = timestamp()
}

resource "terraform_data" "example" {
  input = local.timestamp
}
EOF

Initialize the fixed configuration:

bash
terraform init -input=false
output
Terraform has been successfully initialized!

Validate confirms the function call is now in an allowed context:

bash
terraform validate -no-color
output
Success! The configuration is valid.

Plan and apply to store the computed timestamp:

bash
terraform apply -auto-approve -input=false -no-color
output
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Inspect what landed in state:

bash
terraform state show terraform_data.example
output
input  = "2026-08-13T03:57:23Z"
    output = "2026-08-13T03:57:23Z"

The distinction in plain terms:

text
variable default  → static fallback input the caller can override
local             → calculated expression inside the module

Destroy when you finish so the lab directory stays disposable:

bash
terraform destroy -auto-approve -input=false -no-color

Use functions directly where expressions are allowed

You do not always need a locals block for a one-off calculation. Resource arguments accept function calls when Terraform evaluates them during the plan/apply graph.

bash
mkdir -p ~/terraform-labs/terraform-functions-may-not-be-called-here/fixes/direct-expression
cd ~/terraform-labs/terraform-functions-may-not-be-called-here/fixes/direct-expression
bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
}

resource "terraform_data" "example" {
  input = formatdate("YYYY-MM-DD", timestamp())
}
EOF

Initialize and plan before the first apply:

bash
terraform init -input=false
bash
terraform plan -no-color -input=false
output
# terraform_data.example will be created
  + resource "terraform_data" "example" {
      + input = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

formatdate() and timestamp() are allowed here because terraform_data.input accepts normal expressions. However, timestamp() has special evaluation behavior: Terraform treats its result as unknown during planning and resolves the actual timestamp during apply. Therefore an expression derived from timestamp(), such as formatdate(..., timestamp()), can also remain (known after apply) in the plan. See known after apply for the broader model.

Apply and read the stored date string:

bash
terraform apply -auto-approve -input=false -no-color
bash
terraform state show terraform_data.example
output
input  = "2026-08-13"

That does not mean every block allows arbitrary expressions — backend settings and variable defaults are stricter, as shown later.


Dynamic defaults with a null variable and a local

When you want caller override with a calculated fallback, leave the variable default static and branch in locals:

bash
mkdir -p ~/terraform-labs/terraform-functions-may-not-be-called-here/fixes/dynamic-fallback
cd ~/terraform-labs/terraform-functions-may-not-be-called-here/fixes/dynamic-fallback
bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
}

variable "created_at" {
  type    = string
  default = null
}

locals {
  created_at = var.created_at != null ? var.created_at : timestamp()
}

resource "terraform_data" "example" {
  input = local.created_at
}
EOF

Plan without setting the variable — Terraform selects the calculated fallback expression, but the value returned by timestamp() remains unknown until apply:

bash
terraform init -input=false
bash
terraform plan -no-color -input=false
output
# terraform_data.example will be created
      + input  = (known after apply)
Plan: 1 to add, 0 to change, 0 to destroy.

Pass an explicit value to skip the calculated fallback:

bash
terraform plan -no-color -input=false -var created_at=fixed-value
output
+ input  = "fixed-value"
Plan: 1 to add, 0 to change, 0 to destroy.

The null default is literal and valid. The function call lives in locals, where expressions are allowed.

IMPORTANT
timestamp() returns a new value on every evaluation. When that value is written into state through a resource argument, later plans can show perpetual drift. Use a fixed string for identity fields; reserve timestamp() for one-time labels or debugging, not stable resource keys.

Repeated plans when timestamp() feeds a resource

After apply, a locals { timestamp = timestamp() } value stored in terraform_data.input makes every later plan propose an update.

bash
mkdir -p ~/terraform-labs/terraform-functions-may-not-be-called-here/demos/repeated-plan
cd ~/terraform-labs/terraform-functions-may-not-be-called-here/demos/repeated-plan
bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
}

locals {
  timestamp = timestamp()
}

resource "terraform_data" "example" {
  input = local.timestamp
}
EOF
bash
terraform init -input=false
bash
terraform apply -auto-approve -input=false -no-color

With the stack applied, run the first plan pass:

bash
terraform plan -no-color -input=false
output
# terraform_data.example will be updated in-place
  ~ resource "terraform_data" "example" {
      ~ input  = "2026-08-13T03:57:28Z" -> (known after apply)
      ~ output = "2026-08-13T03:57:28Z" -> (known after apply)
    }
Plan: 0 to add, 1 to change, 0 to destroy.

A second immediate plan shows the same one-change result, and waiting one second does not clear it. The local is valid HCL, but storing a changing expression in terraform_data.input makes every plan want an update. That is expected behavior, not a bug in locals.


Other contexts that restrict function calls

Only include restrictions verified on Terraform 1.15.8 in this lab. Two contexts failed with the same Functions may not be called here message; one related mistake produced different wording.

Variable default (primary case)

Already covered above. Any function call in default fails — including try() with literal arguments:

bash
mkdir -p ~/terraform-labs/terraform-functions-may-not-be-called-here/errors/try-in-variable-default
cd ~/terraform-labs/terraform-functions-may-not-be-called-here/errors/try-in-variable-default
bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
}

variable "port" {
  type    = number
  default = try(tonumber("8080"), 80)
}
EOF
bash
terraform init -input=false
output
Error: Function calls not allowed

  on main.tf line 7, in variable "port":
   7:   default = try(tonumber("8080"), 80)

Functions may not be called here.

Wrapping the call in try() does not change the restriction. Move the logic to locals or a resource argument.

Referencing another variable in a default

A different error — variables are also banned from defaults:

bash
mkdir -p ~/terraform-labs/terraform-functions-may-not-be-called-here/errors/variable-default-var-ref
cd ~/terraform-labs/terraform-functions-may-not-be-called-here/errors/variable-default-var-ref
bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
}

variable "base" {
  type = string
}

variable "name" {
  type    = string
  default = "${var.base}-suffix"
}
EOF
bash
terraform init -input=false
output
Error: Variables not allowed

  on main.tf line 11, in variable "name":
  11:   default = "${var.base}-suffix"

Variables may not be used here.

Use a locals block to combine values instead of chaining variables through defaults.

Backend block path

Backend settings are loaded before the rest of the configuration, so arguments must be static:

bash
mkdir -p ~/terraform-labs/terraform-functions-may-not-be-called-here/additional-tests/backend-join
cd ~/terraform-labs/terraform-functions-may-not-be-called-here/additional-tests/backend-join
bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"

  backend "local" {
    path = join("/", ["terraform.tfstate"])
  }
}
EOF
bash
terraform init -input=false
output
Error: Function calls not allowed

  on main.tf line 5, in terraform:
   5:     path = join("/", ["terraform.tfstate"])

Functions may not be called here.

Fix: use a literal path such as path = "terraform.tfstate" or configure the backend through a partial config file with -backend-config.

Where functions are allowed (contrast)

Not every block restricts functions. An output value may call timestamp():

bash
mkdir -p ~/terraform-labs/terraform-functions-may-not-be-called-here/additional-tests/output-with-function
cd ~/terraform-labs/terraform-functions-may-not-be-called-here/additional-tests/output-with-function
bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
}

output "ts" {
  value = timestamp()
}
EOF
bash
terraform init -input=false
bash
terraform plan -no-color -input=false
output
Changes to Outputs:
  + ts = (known after apply)

Resource and most locals expressions are the normal homes for function calls. See Terraform expressions for evaluation order; this article stays focused on the error case.


Common mistakes

These patterns repeatedly produce the error or a sibling restriction:

  • Calling a function inside a variable default — use a literal default or null, then compute in locals
  • Referencing another variable from a default — use locals to derive combined values
  • Treating a variable default like runtime program logic — defaults are static module inputs, not a place for calculations
  • Using timestamp() for fields that should stay stable in state — expect perpetual plan changes
  • Wrapping the call in try() to dodge the error — try is still a function call in a forbidden context
  • Moving logic to locals but still wondering why plan always changes — the local is valid; the changing value is the problem

Decision guide:

text
Static caller-facing default     → variable default (literal only)
Calculated or reusable value     → locals
One-off in a resource argument   → function call directly in that argument

Troubleshooting

Symptom Likely cause Fix
Functions may not be called here on variable default Function call in variable default Move expression to locals or a resource argument
Variables may not be used here on variable default Default references another variable Combine values in locals instead
try(...) in default still fails try is a function Same fix as any other function in defaults
Error during terraform init on backend Function in backend block argument Use a literal path or -backend-config
Plan always wants to update terraform_data after using timestamp() Value changes every evaluation Use a stable value, or accept perpetual plan changes
Function works in output but not in variable Different expression contexts Not every block has the same rules — match the pattern to the block type

References


Summary

Functions may not be called here means Terraform found a function in an argument that must be known before dynamic evaluation — most often timestamp() or try() inside a variable default. Variable defaults accept literals only; move calculations to locals or call functions directly in resource arguments where expressions are allowed.

timestamp() is allowed in those expression contexts, but its result is unknown during planning and resolved during apply — expressions built from it can show (known after apply) and cause perpetual drift when stored in state. For caller override with a dynamic fallback, default the variable to null and branch in locals. Backend path arguments follow the same static rule.

If you need broader function coverage, read Terraform functions and Terraform locals. This page stays scoped to the exact error and its verified fixes.


Frequently Asked Questions

1. Why does Terraform say functions may not be called in a variable default?

The default argument requires a literal value and cannot reference other configuration objects. Function calls such as timestamp are dynamic expressions and belong in locals or resource arguments instead.

2. Can I use timestamp in a Terraform variable default?

No. timestamp is a function call and variable defaults accept literals only. Declare the variable without a dynamic default, then assign timestamp in a locals block or use it directly in a resource argument where expressions are allowed.

3. What is the difference between a variable default and a local value?

A variable default is a static fallback input the module caller can override. A local is a calculated expression inside the module. Put static caller-facing defaults in variables; put computed values in locals.

4. Will wrapping a function in try fix a variable default error?

No. try is still a function call. Terraform rejects any function in a variable default, including try with literal arguments.

5. Does timestamp in locals cause plan drift on every run?

Yes, when the result is stored in a resource argument. timestamp changes over time and Terraform cannot know its final value during planning, so later plans propose another change and resolve the new timestamp during apply.
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)