Contents


Type constraints control the types of values that Terraform accepts for variables and outputs. Terraform supports primitive types (string, number, bool), collection types (list, map, set), and structural types (object, tuple).


Primitive Types

A primitive type is a simple type that isn’t made from any other types. All primitive types in Terraform are represented by a type keyword. The available primitive types are:

  • string — a sequence of Unicode characters representing some text, such as "hello".
  • number — a numeric value. The number type can represent both whole numbers like 15 and fractional values such as 6.283185.
  • bool — either true or false. bool values can be used in conditional logic.

Conversion of Primitive Types

The Terraform language will automatically convert number and bool values to string values when needed, and vice-versa as long as the string contains a valid representation of a number or boolean value.

  • true converts to "true", and vice-versa
  • false converts to "false", and vice-versa
  • 15 converts to "15", and vice-versa

Complex Types

A complex type is a type that groups multiple values into a single value. Complex types are represented by type constructors, but several of them also have shorthand keyword versions.

There are two categories of complex types: collection types (for grouping similar values), and structural types (for grouping potentially dissimilar values).

Collection Types

A collection type allows multiple values of one other type to be grouped together as a single value. The type of value within a collection is called its element type. All collection types must have an element type, which is provided as the argument to their constructor.

For example, the type list(string) means “list of strings”, which is a different type than list(number), a list of numbers. All elements of a collection must always be of the same type.

The three kinds of collection type in the Terraform language are:

  • list(...) — a sequence of values identified by consecutive whole numbers starting with zero. The keyword list is a shorthand for list(any), which accepts any element type as long as every element is the same type.
  • map(...) — a collection of values where each is identified by a string label. The keyword map is a shorthand for map(any), which accepts any element type as long as every element is the same type.
  • set(...) — a collection of unique values that do not have any secondary identifiers or ordering.

Map values can be defined using {}, :, or =. For example, { "foo": "bar", "bar": "baz" } and { foo = "bar", bar = "baz" } define the same map. You must place map keys in quotation marks when a key starts with a number, contains spaces, or contains special characters. Commas are required between key-value pairs in single-line maps. Multi-line maps can place key-value pairs on new lines.

Structural Types

A structural type allows multiple values of several distinct types to be grouped together as a single value. Structural types require a schema as an argument, to specify which types are allowed for which elements.

The two kinds of structural type in the Terraform language are:

  • object(...) — a collection of named attributes that each have their own type. The schema for object types is { <KEY> = <TYPE>, <KEY> = <TYPE>, ... }. Values that match the object type must contain all of the specified keys, and the value for each key must match its specified type. Values with additional keys can still match an object type, but the extra attributes are discarded during type conversion.
  • tuple(...) — a sequence of elements identified by consecutive whole numbers starting with zero, where each element has its own type. The schema for tuple types is [<TYPE>, <TYPE>, ...]. Values that match the tuple type must have exactly the same number of elements, and the value in each position must match the specified type for that position.

For example, an object type of object({ name=string, age=number }) would match:

{
  name = "John"
  age  = 52
}

A tuple type of tuple([string, number, bool]) would match:

["a", 15, true]

Optional Object Type Attributes

To mark attributes as optional, use the optional modifier in the object type constraint:

variable "with_optional_attribute" {
  type = object({
    a = string                # a required attribute
    b = optional(string)      # an optional attribute
    c = optional(number, 127) # an optional attribute with default value
  })
}

The optional modifier takes one or two arguments:

  • Type — (Required) The first argument specifies the type of the attribute.
  • Default — (Optional) The second argument defines the default value that Terraform should use if the attribute is not present. This must be compatible with the attribute type. If not specified, Terraform uses a null value of the appropriate type as the default.

Nested Structures with Optional Attributes and Defaults

The following example defines a variable for storage buckets that host a website. This variable type uses several optional attributes, including website, which is itself an optional object type that has optional attributes and defaults.

variable "buckets" {
  type = list(object({
    name    = string
    enabled = optional(bool, true)
    website = optional(object({
      index_document = optional(string, "index.html")
      error_document = optional(string, "error.html")
      routing_rules  = optional(string)
    }), {})
  }))
}

Examples

Primitive Types

variable "instance_name" {
  type    = string
  default = "web-server"
}
 
variable "instance_count" {
  type    = number
  default = 3
}
 
variable "enable_monitoring" {
  type    = bool
  default = true
}

List

variable "availability_zones" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
 
variable "instance_ports" {
  type    = list(number)
  default = [80, 443, 8080]
}

Map

variable "instance_tags" {
  type = map(string)
  default = {
    Environment = "production"
    Team        = "platform"
    ManagedBy   = "terraform"
  }
}
 
variable "instance_counts" {
  type = map(number)
  default = {
    dev     = 1
    staging = 2
    prod    = 3
  }
}

Set

variable "allowed_cidrs" {
  type = set(string)
  default = [
    "10.0.0.0/16",
    "172.16.0.0/12",
    "192.168.0.0/24",
  ]
}

Object

variable "database" {
  type = object({
    engine         = string
    instance_class = string
    allocated_storage = number
    multi_az       = bool
  })
  default = {
    engine            = "postgres"
    instance_class    = "db.t3.medium"
    allocated_storage = 50
    multi_az          = true
  }
}

Tuple

variable "rule" {
  type    = tuple([string, number, bool])
  default = ["allow", 443, true]
}

List of Objects

variable "ingress_rules" {
  type = list(object({
    port        = number
    protocol    = string
    cidr_blocks = list(string)
  }))
  default = [
    {
      port        = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    },
    {
      port        = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    },
  ]
}

Map of Objects

variable "environments" {
  type = map(object({
    instance_type = string
    min_size      = number
    max_size      = number
    enable_ssl    = bool
  }))
  default = {
    dev = {
      instance_type = "t3.small"
      min_size      = 1
      max_size      = 2
      enable_ssl    = false
    }
    prod = {
      instance_type = "t3.large"
      min_size      = 2
      max_size      = 10
      enable_ssl    = true
    }
  }
}

Nested Objects

variable "application" {
  type = object({
    name = string
    network = object({
      vpc_cidr = string
      subnets = list(object({
        cidr = string
        az   = string
        type = string
      }))
    })
    database = object({
      engine   = string
      replicas = number
      backup = object({
        enabled        = bool
        retention_days = number
      })
    })
  })
  default = {
    name = "my-app"
    network = {
      vpc_cidr = "10.0.0.0/16"
      subnets = [
        { cidr = "10.0.1.0/24", az = "us-east-1a", type = "public" },
        { cidr = "10.0.2.0/24", az = "us-east-1b", type = "public" },
        { cidr = "10.0.3.0/24", az = "us-east-1a", type = "private" },
        { cidr = "10.0.4.0/24", az = "us-east-1b", type = "private" },
      ]
    }
    database = {
      engine   = "postgres"
      replicas = 2
      backup = {
        enabled        = true
        retention_days = 7
      }
    }
  }
}

Nested with Optional Attributes

variable "services" {
  type = list(object({
    name = string
    port = number
    health_check = optional(object({
      path     = optional(string, "/health")
      interval = optional(number, 30)
      timeout  = optional(number, 5)
    }), {})
    scaling = optional(object({
      min_count  = optional(number, 1)
      max_count  = optional(number, 3)
      cpu_target = optional(number, 70)
    }))
    tags = optional(map(string), {})
  }))
}

Usage in terraform.tfvars:

services = [
  {
    name = "api"
    port = 8080
    health_check = {
      path     = "/api/health"
      interval = 10
    }
    scaling = {
      min_count = 2
      max_count = 10
    }
    tags = { tier = "backend" }
  },
  {
    name = "frontend"
    port = 3000
    # health_check and scaling use defaults
  },
]