Fix Terraform 'Invalid value for list parameter: cannot convert string to list' Error

beginner๐Ÿ—๏ธ Terraform2026-05-09| Terraform 0.13+, any OS (Linux, macOS, Windows), any cloud provider (AWS, GCP, Azure)

Error Message

Invalid value for "list" parameter: cannot convert string to list of any single type.
#terraform#function#type-mismatch#hcl#tolist#toset

The Error

You run terraform plan or terraform apply and get stopped cold:

โ”‚ Error: Invalid function argument
โ”‚
โ”‚   on main.tf line 14, in resource "aws_security_group" "example":
โ”‚   14:   cidr_blocks = tolist(var.allowed_cidr)
โ”‚
โ”‚ Invalid value for "list" parameter: cannot convert string to list of any
โ”‚ single type.

The root cause is a type mismatch. You're passing a string to a function that expects a list or set โ€” such as tolist(), toset(), length(), element(), or join(). Terraform refuses to guess what you meant and fails immediately at plan time.

Why This Happens

Terraform's type system doesn't do silent coercions. Every built-in function has strictly typed parameters, and a wrong type means a hard failure โ€” no warnings, no fallbacks. Four situations trigger this most often:

  • A variable is declared type = string but gets passed to a list-expecting function
  • A module output returns a single string, but the calling module treats it as a list
  • Someone wrote a comma-separated string like "10.0.0.0/8,192.168.0.0/16" assuming Terraform would split it automatically โ€” it won't
  • A data source attribute is a string scalar, not a list (common with aws_ami IDs, ARNs, etc.)

Step-by-Step Fix

Step 1 โ€” Identify the actual type of your value

Open the .tf file mentioned in the error and find the variable or attribute declaration. Here's a typical offender:

# WRONG โ€” declared as string, used as list
variable "allowed_cidr" {
  type    = string
  default = "10.0.0.0/8"
}

resource "aws_security_group" "example" {
  ingress {
    cidr_blocks = tolist(var.allowed_cidr)  # โ† ERROR here
  }
}

var.allowed_cidr is a plain string. tolist() needs a collection โ€” a set or tuple. That mismatch is the entire problem.

Step 2 โ€” Fix the variable type declaration

The cleanest fix: declare the variable as a list from the start.

# CORRECT โ€” declare as list
variable "allowed_cidr" {
  type    = list(string)
  default = ["10.0.0.0/8"]
}

resource "aws_security_group" "example" {
  ingress {
    cidr_blocks = var.allowed_cidr  # No conversion needed
  }
}

Notice you don't even need tolist() anymore. A list(string) variable is already the right type for cidr_blocks โ€” just use it directly.

Step 3 โ€” If you must accept a string, use split() to convert it

CI pipelines, environment variables, and legacy configs often pass values as comma-separated strings. You can't always change the input format. Use split() to turn it into a real list:

variable "allowed_cidr" {
  type    = string
  default = "10.0.0.0/8,192.168.0.0/16"
}

locals {
  cidr_list = split(",", var.allowed_cidr)
}

resource "aws_security_group" "example" {
  ingress {
    cidr_blocks = local.cidr_list
  }
}

Putting the conversion in a local keeps your resource blocks clean and makes the intent obvious to anyone reading the code later.

Step 4 โ€” Use toset() for unique collections (e.g. for_each)

for_each requires a set or map โ€” not a string, not a list. If your variable is still typed as string, this blows up:

# WRONG
resource "aws_iam_user" "example" {
  for_each = toset(var.username)  # var.username is a string โ†’ ERROR
  name     = each.value
}

# CORRECT
variable "usernames" {
  type    = list(string)
  default = ["alice", "bob", "carol"]
}

resource "aws_iam_user" "example" {
  for_each = toset(var.usernames)  # list โ†’ set: OK
  name     = each.value
}

toset() converts a list to a set (deduplicating values in the process). It works on lists โ€” not strings.

Step 5 โ€” Wrap a single string in brackets when needed

Data source attributes like AMI IDs, ARNs, and account IDs are always string scalars. Most of the time that's fine โ€” but occasionally a downstream resource expects a list. Wrap it with brackets:

data "aws_ami" "ubuntu" {
  most_recent = true
  # ...
}

# data.aws_ami.ubuntu.id is a string
resource "aws_launch_template" "example" {
  image_id = data.aws_ami.ubuntu.id        # fine as-is

  # Somewhere that needs a list:
  # security_group_names = [data.aws_ami.ubuntu.id]  โ† wrap it
}

Verify the Fix

Start with terraform validate โ€” it catches type errors instantly, no API calls required:

terraform validate

A clean config outputs:

Success! The configuration is valid.

Then run terraform plan to confirm no runtime type issues remain. If the plan shows your resources with the correct CIDR blocks (or whichever attribute you fixed), you're done.

Quick Cheat Sheet: Type Conversion Functions

  • split(",", string) โ€” string โ†’ list(string), splits on a delimiter
  • tolist(set_or_tuple) โ€” set/tuple โ†’ list (does not work on strings)
  • toset(list) โ€” list โ†’ set, removes duplicates (does not work on strings)
  • [value] โ€” wraps any scalar in a single-element list literal
  • compact(list) โ€” strips empty strings from a list
  • flatten(list_of_lists) โ€” collapses nested lists into one flat list

Tips to Avoid This Error

  • Declare variable types explicitly. Skip type = any โ€” it hides type problems until runtime, usually at the worst possible moment.
  • Read the function docs before using it. Every Terraform built-in has a clear signature showing what types each parameter accepts. Thirty seconds of reading saves twenty minutes of debugging.
  • Test expressions in terraform console before committing them to config:
$ terraform console
> tolist(["a", "b"])
[
  "a",
  "b",
]
> tolist("not-a-list")
โ”‚ Error: Invalid function argument
โ”‚ cannot convert string to list of any single type.
  • Add terraform validate to your CI pipeline. It runs in under a second, requires no credentials, and catches type mismatches before they ever reach plan.

Related Error Notes