Article · part of a guide
Terraform Optionals On Complex Input Variables
Learn how to use the Terraform optional object type attribute in complex input variables.

Key takeaways
- Terraform v1.3 introduced the optional object type attribute, letting you mark individual fields in a complex object variable as optional.
- Before optional() existed, engineers worked around optional fields by passing empty strings or splitting properties into separate variables with defaults, which got messy at scale.
- Writing an attribute as optional(string) makes it optional, and optional(string, 'StorageV2') sets a per-attribute default value when none is supplied.
- Using optional() lets you keep configuration in single complex objects with sensible per-property defaults instead of building separate variables for each optional field.
Terraform v1.3 shipped what I think is one of the most useful features since v1.0: the optional object type attribute. To see why it matters, let's start with a variable for creating a storage account on Azure.
The variables are in their rawest form, with no validation or description.
variable "storage_account" {
type = object({
name = string
resource_group_name = string
location = string
kind = string
tier = string
replication_type = string
})
}This example has all the properties we need to create a storage account on Azure. But the kind property is actually optional, and since optional on an attribute doesn't exist yet, we have to work around that. I've seen two common ways to do it:
storage_account = {
name = "sablt"
resource_group_name = "rg-blt"
location = "australieast"
kind = "" //NOTE: We don't want to set this
tier = "Premium"
replication_type = "LRS"
}This is what we'd set in either a .tfvars file or a module call. We're passing an empty string for kind, and our code would deal with the empty string for us, but we still have to type those four pesky characters. So let's look at the next scenario, which actually requires more typing!
variable "storage_account" {
type = object({
name = string
resource_group_name = string
location = string
tier = string
replication_type = string
})
}
variable "storage_account_kind" {
type = string
default = ""
}Now we have two variables. It could be worse: that entire storage_account variable could be split out into its individual components. And yes, I've seen that happen. We've also set a default for the variable so that we don't ever have to pass anything in if we don't want/need to.
On the surface this doesn't feel so bad, especially for something as simple as this. But extrapolate it to more complex situations, or to cases where many (potentially most) properties are optional, and you're going to end up with a mess of variables. I personally always opt for complex objects as I feel it is far easier to see and understand what's going on and what the requirements of the code are!
Now let's see how the new feature handles the same case.
variable "storage_account" {
type = object({
name = string
resource_group_name = string
location = string
kind = optional(string)
tier = string
replication_type = string
})
}Now kind is of type optional(string), which means that if we do set a value, it has to be a string. It can go a step further, though. What if we want a default value just for this property? Now you can have one!
variable "storage_account" {
type = object({
name = string
resource_group_name = string
location = string
kind = optional(string, "StorageV2")
tier = string
replication_type = string
})
}We probably wouldn't set a default for this particular property, since we mostly want it to be optional. For tier, though, we might want a default that's common for the business and only deviate from it as needed.
optional gives us a much better way to set up complex object variables. We can mark some properties as optional and give others a sensible default. We no longer have to build out separate variables for properties we want to be optional.
You can follow Brendan @BrendanLiamT on Twitter.
Frequently asked questions
How do you make an attribute optional in a Terraform object variable?
Wrap the attribute's type in optional(), for example kind = optional(string) inside an object type definition. Callers can then leave that field out entirely instead of passing a placeholder value. The feature shipped in Terraform v1.3.
Can you set a default value for a single attribute in a Terraform object variable?
Yes. optional() takes a second argument that becomes the default when no value is supplied, for example kind = optional(string, "StorageV2"). This is handy for properties where the business has a common value and you only deviate from it as needed.
How did people handle optional object attributes before Terraform v1.3?
Two workarounds were common: passing an empty string for the attribute and having the code deal with it, or splitting the optional property out into a separate variable with its own default. Both get messy once you have many optional properties, which is why the optional() attribute was such a useful addition.
Why use one complex object variable instead of many separate Terraform variables?
A single complex object makes it far easier to see what's going on and what the code requires, instead of a sprawl of individual variables. With optional() you can keep everything in one object while still marking some properties optional and giving others sensible defaults.
About the author

solutions engineer at Scalr
Brendan Thompson is a solutions engineer at Scalr, specializing in Terraform and cloud infrastructure.
Part of this guide
6 sheets