| --- |
| page_title: Type Constraints - Configuration Language |
| description: >- |
| Learn how to use type constraints to validate user inputs to modules and |
| resources. |
| --- |
| |
| # Type Constraints |
| |
| Terraform module authors and provider developers can use detailed type |
| constraints to validate user-provided values for their input variables and |
| resource arguments. This requires some additional knowledge about Terraform's |
| type system, but allows you to build a more resilient user interface for your |
| modules and resources. |
| |
| ## Type Keywords and Constructors |
| |
| Type constraints are expressed using a mixture of _type keywords_ and |
| function-like constructs called _type constructors._ |
| |
| * Type keywords are unquoted symbols that represent a static type. |
| * Type constructors are unquoted symbols followed by a pair of |
| parentheses, which contain an argument that specifies more information about |
| the type. Without its argument, a type constructor does not fully |
| represent a type; instead, it represents a _kind_ of similar types. |
| |
| Type constraints look like other kinds of Terraform |
| [expressions](/language/expressions), but are a special syntax. Within the |
| Terraform language, they are only valid in the `type` argument of an |
| [input variable](/language/values/variables). |
| |
| ## 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. This is for |
| compatibility with older configurations; for new code, we recommend using |
| the full form. |
| * `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. This is for |
| compatibility with older configurations; for new code, we recommend using |
| the full form. |
| |
| Maps can be made with braces ({}) and colons (:) or equals signs (=): |
| { "foo": "bar", "bar": "baz" } OR { foo = "bar", bar = "baz" }. Quotes |
| may be omitted on keys, unless the key starts with a number, in which |
| case quotes are required. Commas are required between key/value pairs |
| for single line maps. A newline between key/value pairs is sufficient |
| in multi-line maps. |
| |
| -> **Note:** Although colons are valid delimiters between keys and values, `terraform fmt` ignores them. In contrast, `terraform fmt` attempts to vertically align equals signs. |
| |
| * `set(...)`: a collection of unique values that do not have any secondary |
| identifiers or ordering. |
| |
| ### 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>, ... }` — a |
| pair of curly braces containing a comma-separated series of `<KEY> = <TYPE>` |
| pairs. 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>, ...]` — a pair of square |
| brackets containing a comma-separated series of types. Values that match the |
| tuple type must have _exactly_ the same number of elements (no more and no |
| fewer), 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 |
| a value like the following: |
| |
| ```hcl |
| { |
| name = "John" |
| age = 52 |
| } |
| ``` |
| |
| Also, an object type of `object({ id=string, cidr_block=string })` would match |
| the object produced by a reference to an `aws_vpc` resource, like |
| `aws_vpc.example_vpc`; although the resource has additional attributes, they |
| would be discarded during type conversion. |
| |
| Finally, a tuple type of `tuple([string, number, bool])` would match a value |
| like the following: |
| |
| ```hcl |
| ["a", 15, true] |
| ``` |
| |
| ### Complex Type Literals |
| |
| The Terraform language has literal expressions for creating tuple and object |
| values, which are described in |
| [Expressions: Literal Expressions](/language/expressions/types#literal-expressions) as |
| "list/tuple" literals and "map/object" literals, respectively. |
| |
| Terraform does _not_ provide any way to directly represent lists, maps, or sets. |
| However, due to the automatic conversion of complex types (described below), the |
| difference between similar complex types is almost never relevant to a normal |
| user, and most of the Terraform documentation conflates lists with tuples and |
| maps with objects. The distinctions are only useful when restricting input |
| values for a module or resource. |
| |
| ### Conversion of Complex Types |
| |
| Similar kinds of complex types (list/tuple/set and map/object) can usually be |
| used interchangeably within the Terraform language, and most of Terraform's |
| documentation glosses over the differences between the kinds of complex type. |
| This is due to two conversion behaviors: |
| |
| * Whenever possible, Terraform converts values between similar kinds of complex |
| types if the provided value is not the exact type requested. "Similar kinds" |
| is defined as follows: |
| * Objects and maps are similar. |
| * A map (or a larger object) can be converted to an object if it has |
| _at least_ the keys required by the object schema. Any additional |
| attributes are discarded during conversion, which means map -> object |
| -> map conversions can be lossy. |
| * Tuples and lists are similar. |
| * A list can only be converted to a tuple if it has _exactly_ the |
| required number of elements. |
| * Sets are _almost_ similar to both tuples and lists: |
| * When a list or tuple is converted to a set, duplicate values are |
| discarded and the ordering of elements is lost. |
| * When a `set` is converted to a list or tuple, the elements will be |
| in an arbitrary order. If the set's elements were strings, they will |
| be in lexicographical order; sets of other element types do not |
| guarantee any particular order of elements. |
| * Whenever possible, Terraform converts _element values_ within a complex type, |
| either by converting complex-typed elements recursively or as described above |
| in [Conversion of Primitive Types](#conversion-of-primitive-types). |
| |
| For example: if a module argument requires a value of type `list(string)` and a |
| user provides the tuple `["a", 15, true]`, Terraform will internally transform |
| the value to `["a", "15", "true"]` by converting the elements to the required |
| `string` element type. Later, if the module uses those elements to set different |
| resource arguments that require a string, a number, and a bool (respectively), |
| Terraform will automatically convert the second and third strings back to the |
| required types at that time, since they contain valid representations of a |
| number and a bool. |
| |
| On the other hand, automatic conversion will fail if the provided value |
| (including any of its element values) is incompatible with the required type. If |
| an argument requires a type of `map(string)` and a user provides the object |
| `{name = ["Kristy", "Claudia", "Mary Anne", "Stacey"], age = 12}`, Terraform |
| will raise a type mismatch error, since a tuple cannot be converted to a string. |
| |
| ## Dynamic Types: The "any" Constraint |
| |
| The keyword `any` is a special construct that serves as a placeholder for a |
| type yet to be decided. `any` is not _itself_ a type: when interpreting a |
| value against a type constraint containing `any`, Terraform will attempt to |
| find a single actual type that could replace the `any` keyword to produce |
| a valid result. |
| |
| For example, given the type constraint `list(any)`, Terraform will examine |
| the given value and try to choose a replacement for the `any` that would |
| make the result valid. |
| |
| If the given value were `["a", "b", "c"]` -- whose physical type is |
| `tuple([string, string, string])`, Terraform analyzes this as follows: |
| |
| * Tuple types and list types are _similar_ per the previous section, so the |
| tuple-to-list conversion rule applies. |
| * All of the elements in the tuple are strings, so the type constraint |
| `string` would be valid for all of the list elements. |
| * Therefore in this case the `any` argument is replaced with `string`, |
| and the final concrete value type is `list(string)`. |
| |
| All of the elements of a collection must have the same type, so conversion |
| to `list(any)` requires that all of the given elements must be convertible |
| to a common type. This implies some other behaviors that result from the |
| conversion rules described in earlier sections. |
| |
| * If the given value were instead `["a", 1, "b"]` then Terraform would still |
| select `list(string)`, because of the primitive type conversion rules, and |
| the resulting value would be `["a", "1", "b"]` due to the string conversion |
| implied by that type constraint. |
| * If the given value were instead `["a", [], "b"]` then the value cannot |
| conform to the type constraint: there is no single type that both a string |
| and an empty tuple can convert to. Terraform would reject this value, |
| complaining that all elements must have the same type. |
| |
| Although the above examples use `list(any)`, a similar principle applies to |
| `map(any)` and `set(any)`. |
| |
| If you wish to apply absolutely no constraint to the given value, the `any` |
| keyword can be used in isolation: |
| |
| ```hcl |
| variable "no_type_constraint" { |
| type = any |
| } |
| ``` |
| |
| In this case, Terraform will replace `any` with the exact type of the given |
| value and thus perform no type conversion whatsoever. |
| |
| ## Optional Object Type Attributes |
| |
| Terraform typically returns an error when it does not receive a value for specified object attributes. When you mark an attribute as optional, Terraform instead inserts a default value for the missing attribute. This allows the receiving module to describe an appropriate fallback behavior. |
| |
| To mark attributes as optional, use the `optional` modifier in the object type constraint. The following example creates optional attribute `b` and optional attribute with a default value `c`. |
| |
| ```hcl |
| 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. |
| |
| An optional attribute with a non-`null` default value is guaranteed to never have the value `null` within the receiving module. Terraform will substitute the default value both when a caller omits the attribute altogether and when a caller explicitly sets it to `null`, thereby avoiding the need for additional checks to handle a possible null value. |
| |
| Terraform applies object attribute defaults top-down in nested variable types. This means that Terraform applies the default value you specify in the `optional` modifier first and then later applies any nested default values to that attribute. |
| |
| ### Example: 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. |
| |
| ```hcl |
| 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) |
| }), {}) |
| })) |
| } |
| ``` |
| |
| The following example `terraform.tfvars` file specifies three bucket configurations for `var.buckets`. |
| |
| - `production` sets the routing rules to add a redirect |
| - `archived` uses default configuration, but is disabled |
| - `docs` overrides the index and error documents to use text files |
| |
| The `production` bucket does not specify the index and error documents, and the `archived` bucket omits the website configuration entirely. Terraform will use the default values specified in the `bucket` type constraint. |
| |
| ```hcl |
| buckets = [ |
| { |
| name = "production" |
| website = { |
| routing_rules = <<-EOT |
| [ |
| { |
| "Condition" = { "KeyPrefixEquals": "img/" }, |
| "Redirect" = { "ReplaceKeyPrefixWith": "images/" } |
| } |
| ] |
| EOT |
| } |
| }, |
| { |
| name = "archived" |
| enabled = false |
| }, |
| { |
| name = "docs" |
| website = { |
| index_document = "index.txt" |
| error_document = "error.txt" |
| } |
| }, |
| ] |
| ``` |
| |
| This configuration produces the following variable values. |
| |
| - For the `production` and `docs` buckets, Terraform sets `enabled` to `true`. Terraform also supplies default values for `website`, and then the values specified in `docs` override those defaults. |
| - For the `archived` and `docs` buckets, Terraform sets `routing_rules` to a `null` value. When Terraform does not receive optional attributes and there are no specified defaults, Terraform populates those attributes with a `null` value. |
| - For the `archived` bucket, Terraform populates the `website` attribute with the default values specified in the `buckets` type constraint. |
| |
| ```hcl |
| tolist([ |
| { |
| "enabled" = true |
| "name" = "production" |
| "website" = { |
| "error_document" = "error.html" |
| "index_document" = "index.html" |
| "routing_rules" = <<-EOT |
| [ |
| { |
| "Condition" = { "KeyPrefixEquals": "img/" }, |
| "Redirect" = { "ReplaceKeyPrefixWith": "images/" } |
| } |
| ] |
| |
| EOT |
| } |
| }, |
| { |
| "enabled" = false |
| "name" = "archived" |
| "website" = { |
| "error_document" = "error.html" |
| "index_document" = "index.html" |
| "routing_rules" = tostring(null) |
| } |
| }, |
| { |
| "enabled" = true |
| "name" = "docs" |
| "website" = { |
| "error_document" = "error.txt" |
| "index_document" = "index.txt" |
| "routing_rules" = tostring(null) |
| } |
| }, |
| ]) |
| ``` |
| |
| ### Example: Conditionally setting an optional attribute |
| |
| Sometimes the decision about whether or not to set a value for an optional argument needs to be made dynamically based on some other data. In that case, the calling `module` block can use a conditional expression with `null` as one of its result arms to represent dynamically leaving the argument unset. |
| |
| With the `variable "buckets"` declaration shown in the previous section, the following example conditionally overrides the `index_document` and `error_document` settings in the `website` object based on a new variable `var.legacy_filenames`: |
| |
| ```hcl |
| variable "legacy_filenames" { |
| type = bool |
| default = false |
| nullable = false |
| } |
| |
| module "buckets" { |
| source = "./modules/buckets" |
| |
| buckets = [ |
| { |
| name = "maybe_legacy" |
| website = { |
| error_document = var.legacy_filenames ? "ERROR.HTM" : null |
| index_document = var.legacy_filenames ? "INDEX.HTM" : null |
| } |
| }, |
| ] |
| } |
| ``` |
| |
| When `var.legacy_filenames` is set to `true`, the call will override the document filenames. When it is `false`, the call will leave the two filenames unspecified, thereby allowing the module to use its specified default values. |