Terraform Error Guide: 'Invalid function argument' (bad type or value passed to a function)
Fix Terraform's 'Invalid function argument' error: convert types with tostring/tonumber/tolist, fix cidrsubnet ranges, guard inputs with try()/can(), test in console.
- #terraform
- #troubleshooting
- #errors
- #functions
Exact Error Message
Invalid function argument fires when a built-in function receives a value it cannot use — the wrong type, an out-of-range number, a missing file path, or malformed JSON/YAML. Terraform names the function and the parameter that rejected the value.
Error: Invalid function argument
on network.tf line 8, in locals:
8: subnet = cidrsubnet(var.vpc_cidr, 12, 4096)
Invalid value for "netnum" parameter: prefix extension of 12 does not
accommodate a subnet numbered 4096.
Error: Invalid function argument
on data.tf line 3, in locals:
3: cfg = jsondecode(file("${path.module}/config.json"))
Invalid value for "str" parameter: contains an invalid JSON encoding:
invalid character '}' looking for beginning of object key string.
What the Error Means
Terraform’s functions are strictly typed. When you call cidrsubnet, file, jsondecode, element, templatefile, or regex, the runtime validates each argument against the function’s signature before it computes a result. Invalid function argument means one of those arguments passed the parse stage but failed the function’s own validation: a string where a number was needed, a netnum larger than the address space allows, a path that does not exist, or text that is not valid JSON.
Unlike a syntax error, the HCL here is well-formed. The problem is the value flowing into the function at evaluation time, which is why the error often appears only for certain inputs.
Common Causes
- Wrong type passed to a function. Passing a string
"3"where a number is expected, or a single object where a list is required. cidrsubnet/cidrhostout of range. Thenewbitsplus the base prefix exceed/32(IPv4), ornetnum/hostnumis larger than the subnet can hold.file()on a missing path. The path is wrong, relative to the wrong directory, or the file is generated and not yet present.jsondecode/yamldecodeon malformed input. Trailing commas, unquoted keys, or a partial template render produce invalid documents.- Index out of range in
element(). Callingelement(list, n)where the list is empty (an empty list errors even thoughelementwraps the index). templatefilevariable mismatch. The template references a variable not present in thevarsmap, or the map contains a value of an unexpected type.regex/regexallpattern errors. An invalid regular expression, or a pattern with capture groups passed where a single match is expected.
How to Reproduce the Error
# network.tf — cidrsubnet netnum too large for the prefix extension
locals {
vpc_cidr = "10.0.0.0/16"
# 4 new bits => 16 subnets (0-15); 4096 is out of range
bad_subnet = cidrsubnet(local.vpc_cidr, 4, 4096)
}
# data.tf — wrong type into a numeric function
variable "replicas" {
type = string
default = "3"
}
locals {
# max() expects numbers, not strings
capacity = max(var.replicas, 5)
}
terraform plan
Error: Invalid function argument
on network.tf line 5, in locals:
5: bad_subnet = cidrsubnet(local.vpc_cidr, 4, 4096)
Invalid value for "netnum" parameter: prefix extension of 4 does not
accommodate a subnet numbered 4096.
Diagnostic Commands
Find the function and argument Terraform rejected:
terraform plan 2>&1 | grep -A4 "Invalid function argument"
Test the failing expression interactively — terraform console evaluates real functions against your state and variables:
terraform console
> cidrsubnet("10.0.0.0/16", 4, 4096)
> type(var.replicas)
> can(jsondecode(file("${path.module}/config.json")))
Error: Invalid function argument
string
false
Validate a file exists and parses before Terraform touches it:
ls -l config.json
jq empty config.json # exits non-zero on malformed JSON
Step-by-Step Resolution
1. Read the function signature. Match each argument to its expected type. cidrsubnet(prefix, newbits, netnum) needs a CIDR string, a number, and a number; templatefile(path, vars) needs a path and a map.
2. Convert the type explicitly. Wrap mistyped inputs in tostring(), tonumber(), tolist(), or toset():
locals {
capacity = max(tonumber(var.replicas), 5)
}
3. Fix range errors. Ensure netnum < 2 ^ newbits and base_prefix + newbits <= 32:
# /16 + 4 newbits = /20 => valid netnum range is 0..15
local.subnet = cidrsubnet("10.0.0.0/16", 4, 3)
4. Guard fragile inputs with try() and can(). Provide a fallback instead of crashing:
locals {
raw = try(file("${path.module}/config.json"), "{}")
config = try(jsondecode(local.raw), {})
}
can() returns a bool you can use in precondition blocks or validation:
variable "json_path" {
type = string
validation {
condition = can(jsondecode(file(var.json_path)))
error_message = "json_path must point to a valid JSON file."
}
}
5. Confirm in the console. Re-run the corrected expression in terraform console until it returns a value instead of an error, then terraform plan.
Prevention and Best Practices
- Declare precise variable
typeconstraints (number,list(string),map(object({...}))) so mistyped values fail fast at the variable boundary, not deep inside a function. - Wrap any function reading external data (
file,jsondecode,yamldecode,templatefile) intry()with a sensible default. - Compute CIDR layouts once in
localsand reference them, rather than recomputingcidrsubnetwith magic numbers scattered across resources. - Lint JSON/YAML inputs in CI (
jq empty,yamllint) beforeterraform planruns. - Add
validationblocks withcan()to give callers a clear message instead of an opaque function error. - For triage, the free incident assistant can trace the rejected argument back to its source value. More patterns in the Terraform guides.
Related Errors
- Invalid for_each argument — keys unknown at plan time, a related “value cannot be used here” failure.
- Invalid index — accessing a list/map element that does not exist; often pairs with
element()range issues. - Call to function failed — a function raised an error mid-evaluation (e.g.
file()on a missing path) rather than rejecting an argument. - Invalid value for input variable — a value fails a
validationblock, frequently thecan()guards you add to prevent this error.
Frequently Asked Questions
Why does cidrsubnet say my netnum is out of range when the number looks small?
The valid range is 0 to 2^newbits - 1. With newbits = 4 you only get 16 subnets (0-15), so even netnum = 16 is out of range. Increase newbits to enlarge the address space, or lower netnum.
How do I stop file() from crashing when the file might not exist?
Wrap it: try(file(path), ""). file() errors if the path is missing, so try() supplies a fallback. For optional config, combine with jsondecode: try(jsondecode(file(path)), {}).
What is the difference between try() and can()?
try() returns the first expression that succeeds (a value or fallback). can() returns a boolean — true if the expression evaluates without error — and is meant for condition/validation blocks where you need a yes/no.
My templatefile fails with an invalid argument about a missing variable. Why?
The template references ${something} that is not a key in the vars map you passed. Every interpolation in the template must have a matching key. Pass all referenced variables, or remove the unused interpolation from the template.
Can terraform console evaluate functions against my real variables?
Yes. terraform console loads your variables, locals, data sources, and state, so cidrsubnet(var.vpc_cidr, 4, 3) evaluates exactly as it would during plan. It is the fastest way to reproduce and fix function argument errors.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.