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

  1. Stringify each element: { for k, v in var.m : k => [for s in v : tostring(s)] }.
  2. Ensure the variable is typed map(list(string)) so Terraform coerces at the boundary.
  3. 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

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


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/f97dbc2f224a9e5d. Report an issue: GitHub.