hashicorp/terraform · error
argument must be a string, a collection type, or a…
Error message
argument must be a string, a collection type, or a structural type
What it means
Thrown by the `length()` HCL function (LengthFunc) when its single argument's cty type is not one of string, tuple, object, list, map, set, or dynamic. The Type callback enumerates the accepted types and rejects everything else (numbers, booleans) at type-check time, before the implementation runs. Numbers and booleans have no meaningful length, so they are rejected outright.
Solutions
- Pass a value of an accepted type: string, list, set, map, tuple, or object. Inspect the variable declaration and confirm it is a collection.
- If you meant the magnitude of a number, numbers are used directly — remove the length() call.
- If the value is computed/unknown, ensure it is at least typed as a collection; dynamic values are allowed but a definitely-number type is not.
Example fix
// before
output "n" { value = length(var.replica_count) } # var.replica_count is number
// after
output "n" { value = length(var.replica_names) } # var.replica_names is list(string) Defensive patterns
Strategy: validation
Validate before calling
# HCL: confirm the variable is a collection before calling length()
locals {
ok = can([for v in var.input : v]) # true only for list/set/tuple
n = local.ok ? length(var.input) : 0
} Type guard
# HCL guard using try() to fall back on non-collections
locals { n = try(length(var.input), 0) } Prevention
- Declare variables with explicit collection types (list(...), map(...), set(...)) so misuse is caught at validation.
- Use `terraform validate` in CI to surface length() type errors before apply.
- Run `terraform fmt` + editor type hints to catch scalar-vs-collection mix-ups early.
When it happens
Trigger: Calling `length()` in an HCL/Terraform config with a number or boolean value, e.g. `length(var.replicas)` where var.replicas is `number`. The Type func switch falls to the default branch and returns this error.
Common situations: Mis-naming a variable so a scalar (count, bool flag, port number) is passed instead of the intended list/map. Refactoring a variable from list(number) to number and forgetting to update its `length()` call. Passing a single string char count where `strlen` semantics differ.
Related errors
- argument must be a list or tuple
- input must be a map of lists of strings
- all arguments must have the same type
- cannot search an empty list
- input list must not contain null string
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/bff2a95a9a0f7079.
Report an issue: GitHub.
Appendix: source
Thrown at internal/lang/funcs/collection.go:35
)
var LengthFunc = function.New(&function.Spec{
Params: []function.Parameter{
{
Name: "value",
Type: cty.DynamicPseudoType,
AllowDynamicType: true,
AllowUnknown: true,
AllowMarked: true,
},
},
Type: func(args []cty.Value) (cty.Type, error) {
collTy := args[0].Type()
switch {
case collTy == cty.String || collTy.IsTupleType() || collTy.IsObjectType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType:
return cty.Number, nil
default:
return cty.Number, errors.New("argument must be a string, a collection type, or a structural type")
}
},
RefineResult: refineNotNull,
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
coll := args[0]
collTy := args[0].Type()
marks := coll.Marks()
switch {
case collTy == cty.DynamicPseudoType:
return cty.UnknownVal(cty.Number).WithMarks(marks), nil
case collTy.IsTupleType():
l := len(collTy.TupleElementTypes())
return cty.NumberIntVal(int64(l)).WithMarks(marks), nil
case collTy.IsObjectType():
l := len(collTy.AttributeTypes())
return cty.NumberIntVal(int64(l)).WithMarks(marks), nil
case collTy == cty.String:View on GitHub (pinned to d32a084675)