Terraform’s variable block supports a set of optional arguments that shape how a value is typed, defaulted, validated, and exposed to module callers: type, default, description, validation, sensitive, nullable, ephemeral, const, and deprecated. On top of that, Terraform provides a separate optional() function for marking individual attributes inside an object type constraint as optional.
In this article, we will take a look at Terraform variables and the optional arguments they use. We will explain what they are, how to reference them, why they are useful, and how to use type constraints to enhance the quality of your code.
We will cover:
What are the optional arguments for variable declarations in Terraform?
Optional arguments in Terraform can be used when specifying variables to define their behavior and characteristics. These arguments allow you to control how the variables are used and provide defaults, constraints, and metadata or omit certain settings. They enhance the quality of your code and make your infrastructure code more adaptable to different environments without requiring changes to the core logic.
How to define an optional variable in Terraform?
When you define a variable in Terraform, it can simply be defined as:
variable "example_var" {
}The most commonly used optional arguments for variable declarations in Terraform include:
- type
- default
- description
- validation
- sensitive
- ephemeral
- const
- deprecated
- nullable
An example variable declaration could look like this with all the optional parameters included:
variable "example_var" {
type = string
default = "chewbacca"
description = "8 foot tall wookie"
sensitive = false
nullable = false
validation {
condition = var.example_var == "chewbacca"
error_message = "Value must be a wookie."
}
}Why are the optional parameters useful?
Depending on your use case, the main benefits of using optional parameters or combinations of the optional parameters in your variables are adaptability and code quality. They are used to further define variables’ behavior and characteristics, making your variables more structured and controlled and your Terraform configurations more robust and user-friendly.
Read more about Terraform variables.
How to use optional arguments?
Let’s look at each type of Terraform optional variable values.
1. type
The type argument specifies the expected data type of the variable to help Terraform validate that the value provided for the variable matches the expected type.
variable "example_var" {
type = string
}2. default
The default argument provides the default value for the variable. If no value is explicitly passed when applying the configuration, Terraform will use the default value.
variable "example_var" {
type = string
default = "default_value"
}3. description
The description argument allows you to add a human-readable description to the variable. This description can help other users understand the purpose of the variable. As a suggested good practice, you can use the same description as the Terraform docs.
For example, the name variable description from the Azure Virtual Network page:
variable "name" {
type = string
description = "(Required) The name of the virtual network. Changing this forces a new resource to be created."
}4. validation
The validation optional argument enables you to specify a validation rule using a function that the value of the variable must pass. If the validation fails, Terraform will raise an error.
variable "example_var" {
type = number
validation {
condition = var.example_var > 0
error_message = "Value must be greater than 0."
}
}5. sensitive
When sensitive optional argument is set to true, the value of the variable is treated as sensitive information. Terraform will take care to prevent the value from being shown in logs and outputs. Note that Terraform will still record sensitive values in the state, so anyone who can access the state data will have access to the sensitive values in cleartext.
variable "password" {
type = string
sensitive = true
}6. nullable
The nullable argument in a variable block controls whether the module caller may assign the value null to the variable.
Anullvalue is one that represents absence or omission. If you set an argument of a resource to null, Terraform behaves as though you had completely omitted it — it will use the argument’s default value if it has one or raises an error if the argument is mandatory.
variable "example" {
type = string
nullable = false
}7. ephemeral (Terraform 1.10+)
The ephemeral argument makes a variable available at runtime but prevents its value from being stored in plan or state files. This is ideal for short-lived tokens or secrets that should never be persisted.
variable "session_token" {
type = string
description = "Short-lived session token used during apply."
sensitive = true
ephemeral = true
}Ephemeral variables still behave like normal variables inside a single plan/apply run, but you can only use them in certain places (for example, in other ephemeral values, locals, or ephemeral resources).
8. const
The const argument marks a variable as one that must hold a known, constant value at terraform init time, before any plan or apply happens. This lets you reference the variable in places that are evaluated during initialization — most notably a module block’s source and version arguments, which normally can’t take variables at all.
variable "module_version" {
type = string
default = "6.6.0"
description = "Version of the VPC module to install."
const = true
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = var.module_version
}Because the value has to be known at init, a const = true variable cannot depend on data sources, resource attributes, or anything else that’s only resolved during planning. Its default (or whatever value is supplied via tfvars, CLI, or environment) must be a literal that Terraform can resolve before it walks the configuration graph.
9. deprecated
The deprecated argument (available in Terraform v1.15 and later) lets module authors flag a variable as deprecated and surface a custom warning message whenever a caller sets it. The variable still works; Terraform just prints the message during validate, plan, and apply, so consumers know to migrate before you remove it in a future release.
variable "instance_size" {
type = string
default = null
deprecated = "Use 'instance_type' instead. 'instance_size' will be removed in v3.0."
}
variable "instance_type" {
type = string
default = "t3.micro"
description = "EC2 instance type."
}This is the cleanest way to evolve a module’s public interface without a breaking change: ship the new variable, mark the old one as deprecated, and remove it in the next major version. The same argument works on output blocks, with one restriction. I can only be set on outputs in child modules, not in the root module.
When you call a module whose deprecations you can’t (or don’t want to) fix, set ignore_nested_deprecations = true on the module block to mute warnings from that module and any of its nested modules. Your own code’s deprecation warnings will still surface normally.
module "legacy_vpc" {
source = "./modules/legacy-vpc"
ignore_nested_deprecations = true
}How to use the Terraform optional()?
Sections above cover the arguments of the variable block itself. Terraform also provides a related but distinct feature, the optional() modifier (often called a function), which makes individual attributes inside an object type constraint optional.
It was introduced as an experimental feature in Terraform 0.14 and reached general availability in Terraform 1.3. It is also supported in OpenTofu.
The syntax is as follows:
optional(<type>, [<default>])<type>– The type constraint for the attribute (string,number,list(string), anotherobject(...), etc.)<default>(optional) – The value Terraform substitutes when the caller omits the attribute. If you don’t supply a default, Terraform usesnull.
Use optional() when you want a single variable to accept structured input where some fields can be left out by the caller. This is common in module design, where you want sensible defaults for most fields and only require the caller to specify the ones that matter to them.
variable "instances" {
type = map(object({
instance_type = optional(string, "t3.micro")
ami_id = optional(string) # defaults to null
tags = optional(map(string), {})
monitoring = optional(bool, false)
}))
default = {}
}A caller can now provide just the fields they care about:
instances = {
web = {
ami_id = "ami-0c55b159cbfafe1f0"
}
worker = {
instance_type = "t3.large"
ami_id = "ami-0c55b159cbfafe1f0"
monitoring = true
}
}Both entries are valid. The omitted fields receive their optional() defaults.
Terraform optional variable usage example
Now that we’ve covered the optional() function, here’s how it solves a real module-design problem.
Imagine you’re writing a module that provisions a set of EC2 instances. Each instance might need a different instance_type, ami_id, or set of tags, but most of the time you want sensible defaults. Without optional(), callers either have to fully populate every object, or you fall back on workarounds that sacrifice type safety. With optional(), the variable becomes self-documenting and the call site stays clean:
resource "aws_instance" "this" {
for_each = var.instances
instance_type = each.value.instance_type
ami = each.value.ami_id
tags = merge(
{ Name = each.key },
each.value.tags,
)
}
variable "instances" {
type = map(object({
instance_type = optional(string, "t3.micro")
ami_id = optional(string, "ami-0c55b159cbfafe1f0")
tags = optional(map(string), {})
}))
default = {}
}
A caller now only has to specify the fields they want to override. Everything else falls back to the defaults declared inside optional():
# terraform.tfvars
instances = {
web = {} # all defaults
worker = { instance_type = "t3.large" } # one override
bastion = {
instance_type = "t3.small"
ami_id = "ami-09a9858973b288bdd"
tags = { Role = "bastion" }
}
}This produces three instances. web uses the defaults for all three attributes. worker keeps the default AMI and tags but uses a larger instance type. bastion overrides everything. The type system still validates each attribute, so a caller who passes instance_type = 42 gets a clear error at plan time rather than a confusing failure deeper in the provider.
The legacy alternative: lookup()
Before Terraform 1.3, the only way to handle missing attributes in an object variable was the lookup() function, which takes a map, a key, and a default value. The same module would have looked like this:
resource "aws_instance" "this" {
for_each = var.instances
instance_type = lookup(each.value, "instance_type", "t3.micro")
ami = lookup(each.value, "ami_id", "ami-0c55b159cbfafe1f0")
tags = merge(
{ Name = each.key },
lookup(each.value, "tags", {}),
)
}
variable "instances" {
type = map(any)
description = <<-EOT
Map of instances. Each value may contain:
instance_type = string
ami_id = string
tags = map(string)
EOT
}The pattern still works, but it has real downsides:
- The variable type has to be
map(any), so you lose per-attribute type checking. Passinginstance_type = 42slips through at plan time and only fails when the provider rejects it. - The expected schema only lives in the
description, where it can drift from the resource code without anyone noticing. - Every attribute needs a separate
lookup()call at the call site, which clutters resource blocks as the variable grows.
If you’re on Terraform 1.3 or later, or any version of OpenTofu, prefer optional(). Use lookup() only when you have to support older Terraform versions.
Type constraints
Using the type constraint when defining an input variable specifies its expected data type. If no type constraint is set, then a value of any type is accepted. If both the type and default arguments are specified, the given default value must be convertible to the specified type.
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.
variable "example_var" {
type = number
}Acceptable ‘primitive’ type values in Terraform are:
- string
- bool
- number
- any (indicates a value of any type is accepted).
The type constructors also allow you to specify complex types such as collections:
list(<TYPE>)set(<TYPE>)map(<TYPE>)object({<ATTR NAME> = <TYPE>, ... })tuple([<TYPE>, ...])
Why use Spacelift with Terraform?
Terraform is really powerful, but to achieve an end-to-end secure GitOps approach, you need a platform that can orchestrate your Terraform workflows. Spacelift is the infrastructure orchestration platform built for the AI-accelerated software era.
It manages the full lifecycle for both traditional infrastructure as code (IaC) and AI-provisioned infrastructure, giving you access to a powerful CI/CD workflow and unlocking features such as:
- Policies (based on Open Policy Agent) – You can control how many approvals you need for runs, what kind of resources you can create, and what kind of parameters these resources can have, and you can also control the behavior when a pull request is open or merged.
- Multi-IaC workflows – Combine Terraform with Kubernetes, Ansible, and other IaC tools such as OpenTofu, Pulumi, and CloudFormation, create dependencies among them, and share outputs.
- Build self-service infrastructure – You can use Templates and Blueprints to build self-service infrastructure; simply complete a form to provision infrastructure based on Terraform and other supported tools.
- AI-powered provisioning and diagnostics – Spacelift Intelligence adds an AI-powered layer for natural language provisioning, diagnostics, and operational insight across your infrastructure workflows.
- Integrations with any third-party tools – You can integrate with your favorite third-party tools and even build policies for them. For example, see how to integrate security tools in your workflows using Custom Inputs.
Spacelift enables you to create private workers inside your infrastructure, which helps you execute Spacelift-related workflows on your end. Read the documentation for more information on configuring private workers.
You can check it for free by creating a trial account or requesting a demo with one of our engineers.
Key points
Variables can be specified with optional parameters to enhance the quality of your code. Optional variables in Terraform modules allow you to customize module behavior without requiring all parameters to be explicitly defined. Including appropriate optional parameters in variables depending on your use case can be useful. As a best practice, you should at least include a description to improve your code’s readability, and you may consider adding other parameters that are unnecessary.
Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.
Orchestrate Terraform deployments with Spacelift
Orchestrate your Terraform workflows and build governed pipelines using policy as code, programmatic configuration, context sharing, drift detection, resource visualization, and many more.
Frequently asked questions
What's the difference between nullable and optional() in Terraform?
nullable is a setting on an input variable that controls whether the variable itself can accept a null value, while optional() is a type modifier used inside object types to mark specific attributes as not required and optionally assign a default.
How do you make a Terraform variable optional without a default?
Set nullable = true on the variable and omit the default argument, then handle the null case in your configuration. Without a default, callers must still pass a value (even if explicitly null), since only variables with a default are truly optional to supply.
How do you deprecate a Terraform variable?
In Terraform 1.15 and later, add a deprecated argument to the variable block with a message, for example deprecated = “Use new_var instead; will be removed in v2.0”. Terraform emits a warning whenever a value is passed to that variable.
