hashicorp/terraform · error
input must be a map of lists of strings
Error message
input must be a map of lists of strings
What it means
Thrown by TransposeFunc.Impl when an inner element is non-null but its type is not cty.String. transpose() is declared to take map(list(string)) and re-verifies each element is a string; a non-string element violates the contract.
Source
Thrown at internal/lang/funcs/collection.go:594
if !inputMap.IsWhollyKnown() {
return cty.UnknownVal(retType), nil
}
outputMap := make(map[string]cty.Value)
tmpMap := make(map[string][]string)
for it := inputMap.ElementIterator(); it.Next(); {
inKey, inVal := it.Element()
if inVal.IsNull() {
return cty.MapValEmpty(cty.List(cty.String)), errors.New("input must not contain null list")
}
for iter := inVal.ElementIterator(); iter.Next(); {
_, val := iter.Element()
if val.IsNull() {
return cty.MapValEmpty(cty.List(cty.String)), errors.New("input list must not contain null string")
}
if !val.Type().Equals(cty.String) {
return cty.MapValEmpty(cty.List(cty.String)), errors.New("input must be a map of lists of strings")
}
outKey := val.AsString()
if _, ok := tmpMap[outKey]; !ok {
tmpMap[outKey] = make([]string, 0)
}
outVal := tmpMap[outKey]
outVal = append(outVal, inKey.AsString())
sort.Strings(outVal)
tmpMap[outKey] = outVal
}
}
for outKey, outVal := range tmpMap {
values := make([]cty.Value, 0)
for _, v := range outVal {
values = append(values, cty.StringVal(v))
}View on GitHub (pinned to c9def3e214)
Solutions
- Stringify each element: { for k, v in var.m : k => [for s in v : tostring(s)] }.
- Ensure the variable is typed map(list(string)) so Terraform coerces at the boundary.
- Build the input from string literals/attributes only.
Example fix
# before
locals { t = transpose(var.groups) } # numbers inside -> error
# after
locals {
strmap = { for k, v in var.groups : k => [for s in v : tostring(s)] }
t = transpose(local.strmap)
} Defensive patterns
Strategy: type-guard
Validate before calling
# coerce every inner element to string before transpose
locals {
strmap = { for k, v in var.groups : k => [for s in v : tostring(s)] }
t = transpose(local.strmap)
} Type guard
locals {
strmap = { for k, v in var.groups : k => [for s in v : tostring(s)] }
ok = alltrue([for _, list in local.strmap : alltrue([for s in list : s != null && can(tostring(s))])])
t = local.ok ? transpose(local.strmap) : {}
} Try / catch
locals { t = try(transpose({ for k, v in var.groups : k => [for s in v : tostring(s)] }), {}) } Prevention
- Type variables as map(list(string)) so non-strings are coerced or rejected at validation.
- Build inner lists from string literals/attributes only.
- Use tostring() defensively when ingesting JSON/numeric data.
When it happens
Trigger: transpose({a = [1, 2]}) (numbers), transpose({a = [true]}); a map whose lists contain numbers or bools due to loose typing upstream.
Common situations: Numeric IDs where strings were expected; a data structure whose element type wasn't coerced; JSON decode producing numbers.
Related errors
- argument must be a string, a collection type, or a structura
- all arguments must have the same type
- argument must be a list or tuple
- keys and searchset must be of the same type
- input must not contain null list
AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07).
Data as JSON: /api/errors/f97dbc2f224a9e5d.
Report an issue: GitHub.