| Tested on | Ubuntu 26.04 LTS (Resolute Raccoon) |
|---|---|
| Package | terraform 1.15.8-1 |
| Applies to | Any host with Terraform installed |
| Lab environment | Single Ubuntu VM with Terraform — Terraform lab environment on Ubuntu |
| Privilege | Normal user; sudo only if Terraform is not installed yet |
| Scope | Troubleshooting Terraform Attempt to get attribute from null value — null parent object access, optional attributes versus entire object null, default empty object patterns, conditional guards, try normalization, nested null parents, module input design, terraform console diagnostics, and common nullable misconceptions. Does not replace full object type tutorials or every try() pattern. |
| Related guides | Optional object attributes Terraform try vs can Terraform data types Terraform variables terraform console |
Attempt to get attribute from null value means an expression used dot notation on a value Terraform holds as null:
var.settings.name when var.settings is nullThe configuration can pass terraform validate — the failure often appears only when terraform plan evaluates the expression against the final variable values.
Each scenario uses its own directory under ~/terraform-labs/terraform-null-value-error/. Examples use built-in terraform_data only.
What Attempt to get attribute from null value means
Terraform distinguishes two situations that look similar in application code but behave differently in HCL:
| Situation | What Terraform holds | Reading .profile on the parent |
|---|---|---|
| Object exists, optional key omitted | Non-null object; optional() fills or nulls the attribute |
Works — parent is not null |
Entire object is null |
No object at all | Fails — null has no attributes |
The error text names the null value directly:
Error: Attempt to get attribute from null value
on main.tf line 11, in resource "terraform_data" "svc":
11: input = var.settings.name
├────────────────
│ var.settings is null
This value is null, so it does not have any attributes.optional() on inner attributes, nullable = true, and try() solve different problems. None of them automatically make null.name valid.
Reproduce the null parent attribute error
The everyday case is a nullable object variable with a default of null, then a direct attribute reference in a resource argument.
mkdir -p ~/terraform-labs/terraform-null-value-error/errors/null-parent
cd ~/terraform-labs/terraform-null-value-error/errors/null-parentWrite a nullable settings object and read .name without a guard:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
}
variable "settings" {
type = object({
name = string
})
default = null
}
resource "terraform_data" "svc" {
input = var.settings.name
}
EOFInitialize the working directory:
terraform init -input=falseTerraform has been successfully initialized!validate may still pass because it does not always evaluate every variable default against every expression:
terraform validate -no-colorSuccess! The configuration is valid.Plan evaluates var.settings.name against the default null and stops:
terraform plan -no-color -input=falsePlanning failed. Terraform encountered an error while generating this plan.
Error: Attempt to get attribute from null value
on main.tf line 11, in resource "terraform_data" "svc":
11: input = var.settings.name
├────────────────
│ var.settings is null
This value is null, so it does not have any attributes.Inspect the variable in terraform console when the error message is not enough:
echo 'var.settings' | terraform console -no-colornull /* object */The type constraint still describes an object shape, but the value at plan time is null.
Why optional attributes do not fix a null parent
optional() marks keys that callers may omit on a non-null object. It does not turn a null parent into an empty object.
Compare an object that exists with an omitted optional field against a null parent.
mkdir -p ~/terraform-labs/terraform-null-value-error/demos/omitted-vs-null
cd ~/terraform-labs/terraform-null-value-error/demos/omitted-vs-nullDefine an object with one required and one optional attribute:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
}
variable "settings" {
type = object({
name = string
profile = optional(string, "default")
})
}
output "profile_from_object" {
value = var.settings.profile
}
EOFInitialize before planning with a partial object:
terraform init -input=falsePlan with only name supplied — the object exists and profile is omitted:
terraform plan -no-color -input=false -var='settings={ name = "web" }'Changes to Outputs:
+ profile_from_object = "default"
You can apply this plan to save these new output values to the Terraform
state, without changing any real infrastructure.optional(string, "default") filled the missing key. The parent was never null.
Now point the same expression at a null parent. Return to the error directory and add optional() on profile:
cd ~/terraform-labs/terraform-null-value-error/errors/null-parentReplace main.tf so profile is optional but the variable default stays null:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
}
variable "settings" {
type = object({
name = string
profile = optional(string, "default")
})
default = null
}
resource "terraform_data" "svc" {
input = var.settings.profile
}
EOFRe-run plan — optional() never runs because there is no object to apply defaults to:
terraform plan -no-color -input=falseError: Attempt to get attribute from null value
on main.tf line 14, in resource "terraform_data" "svc":
14: input = var.settings.profile
├────────────────
│ var.settings is null
This value is null, so it does not have any attributes.object exists with omitted optional attribute → parent is non-null; optional() applies
entire object = null → no parent; .profile is invalidFor full optional() semantics, see Optional object attributes.
Fix with a non-null default object
When every caller should receive a usable object, replace default = null with a concrete object or an empty object whose type allows it.
default = {} works only when every attribute is optional or carries its own optional() default. A required name field rejects an empty default at validate time.
Reproduce the incompatible default first:
mkdir -p ~/terraform-labs/terraform-null-value-error/errors/incompatible-default
cd ~/terraform-labs/terraform-null-value-error/errors/incompatible-defaultDeclare a required name with default = {}:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
}
variable "settings" {
type = object({
name = string
profile = optional(string, "default")
})
default = {}
}
EOFInitialize and validate:
terraform init -input=falseValidate catches the empty default before plan:
terraform validate -no-colorError: Invalid default value for variable
on main.tf line 10, in variable "settings":
10: default = {}
This default value is not compatible with the variable's type constraint:
attribute "name" is required.When all fields are optional, default = {} is valid and each optional() default applies:
mkdir -p ~/terraform-labs/terraform-null-value-error/fixes/default-all-optional
cd ~/terraform-labs/terraform-null-value-error/fixes/default-all-optionalWrite an all-optional object with default = {}:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
}
variable "settings" {
type = object({
name = optional(string, "web")
profile = optional(string, "default")
})
default = {}
}
resource "terraform_data" "svc" {
input = "${var.settings.name}:${var.settings.profile}"
}
EOFInitialize and confirm the configuration is valid:
terraform init -input=false && terraform validate -no-colorSuccess! The configuration is valid.Plan shows both optional defaults resolved without a null parent:
terraform plan -no-color -input=false# terraform_data.svc will be created
+ resource "terraform_data" "svc" {
+ input = "web:default"
}
Plan: 1 to add, 0 to change, 0 to destroy.Prefer a named default object such as { name = "web" } when one field is required — that documents the expected shape more clearly than {}.
Fix with conditional expressions
When null means "feature disabled" rather than "use defaults", test the parent before you read attributes:
mkdir -p ~/terraform-labs/terraform-null-value-error/fixes/conditional-guard
cd ~/terraform-labs/terraform-null-value-error/fixes/conditional-guardKeep default = null but branch in a local:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
}
variable "settings" {
type = object({
name = string
})
default = null
}
locals {
display_name = var.settings == null ? "unset" : var.settings.name
}
resource "terraform_data" "svc" {
input = local.display_name
}
EOFInitialize and validate:
terraform init -input=false && terraform validate -no-colorSuccess! The configuration is valid.Plan with the default null settings object:
terraform plan -no-color -input=false# terraform_data.svc will be created
+ resource "terraform_data" "svc" {
+ input = "unset"
}
Plan: 1 to add, 0 to change, 0 to destroy.The conditional never calls .name on null. Use the same pattern for nested parents — test var.config.settings == null before var.config.settings.name.
Fix nested null parent objects
Nested optional(object({ ... })) without a default on the optional wrapper leaves the inner object null when callers set settings = null.
mkdir -p ~/terraform-labs/terraform-null-value-error/errors/nested-null-no-default
cd ~/terraform-labs/terraform-null-value-error/errors/nested-null-no-defaultReference a nested .name while settings is explicitly null:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
}
variable "config" {
type = object({
settings = optional(object({
name = string
}))
})
default = {
settings = null
}
}
resource "terraform_data" "svc" {
input = var.config.settings.name
}
EOFInitialize the directory:
terraform init -input=falsePlan surfaces the nested null parent error:
terraform plan -no-color -input=falseError: Attempt to get attribute from null value
on main.tf line 17, in resource "terraform_data" "svc":
17: input = var.config.settings.name
├────────────────
│ var.config.settings is null
This value is null, so it does not have any attributes.Guard the nested parent the same way as a top-level object:
mkdir -p ~/terraform-labs/terraform-null-value-error/fixes/nested-guard
cd ~/terraform-labs/terraform-null-value-error/fixes/nested-guardWrite a conditional that tests the nested object before reading .name:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
}
variable "config" {
type = object({
settings = optional(object({
name = string
}))
})
default = {
settings = null
}
}
locals {
settings_name = var.config.settings == null ? "unset" : var.config.settings.name
}
resource "terraform_data" "svc" {
input = local.settings_name
}
EOFInitialize and confirm plan succeeds:
terraform init -input=false && terraform plan -no-color -input=false# terraform_data.svc will be created
+ resource "terraform_data" "svc" {
+ input = "unset"
}
Plan: 1 to add, 0 to change, 0 to destroy.If you add a default object to optional(object({ ... }), { name = "fallback" }), Terraform replaces explicit null with that default object — attribute access then works, but callers who passed null may not get the behavior they expected. See Optional object attributes for omitted-key versus explicit-null semantics.
When try() helps
try() evaluates expressions left to right and returns the first result that does not error. It can supply a fallback when the parent is null or an attribute is missing — but it also hides schema mistakes if you use it everywhere.
Use it in a normalization local with a documented default:
mkdir -p ~/terraform-labs/terraform-null-value-error/fixes/try-normalize
cd ~/terraform-labs/terraform-null-value-error/fixes/try-normalizeWrap the attribute read in try() once, then reference the local elsewhere:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
}
variable "settings" {
type = object({
name = string
profile = optional(string)
})
default = null
}
locals {
profile = try(var.settings.profile, "default")
}
resource "terraform_data" "svc" {
input = local.profile
}
EOFInitialize and plan with the default null parent:
terraform init -input=false && terraform plan -no-color -input=false# terraform_data.svc will be created
+ resource "terraform_data" "svc" {
+ input = "default"
}
Plan: 1 to add, 0 to change, 0 to destroy.try(var.settings.profile, "default") fails over to "default" because var.settings is null. Limits to remember:
try()does not catch undeclared references — those fail at validate time.- Wrapping every dot in
try()masks typos such as.profleinstead of.profile. - When
nullis meaningful, a conditional expresses intent more clearly thantry().
For broader try versus can patterns, see Terraform try vs can.
Better module input design
Module variables are the right place to enforce a non-null default object so root callers never hit a null parent inside the child module.
mkdir -p ~/terraform-labs/terraform-null-value-error/fixes/optional-module-input/modules/app
cd ~/terraform-labs/terraform-null-value-error/fixes/optional-module-inputChild module with optional(), a concrete default object, and nullable = false:
cat > modules/app/variables.tf <<'EOF'
variable "settings" {
type = object({
name = string
profile = optional(string, "default")
})
nullable = false
default = {
name = "web"
}
}
EOFnullable = false prevents callers from replacing the module's object default with null. Without it, a root module can pass settings = null and the child still receives null even though the variable declares a default — var.settings.name then fails with the same null-attribute error.
Child resource reads attributes on the guaranteed non-null object:
cat > modules/app/main.tf <<'EOF'
resource "terraform_data" "svc" {
input = "${var.settings.name}:${var.settings.profile}"
}
EOFRoot module calls the child with no arguments:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
}
module "app" {
source = "./modules/app"
}
EOFInitialize and plan:
terraform init -input=false && terraform plan -no-color -input=false# module.app.terraform_data.svc will be created
+ resource "terraform_data" "svc" {
+ input = "web:default"
}
Plan: 1 to add, 0 to change, 0 to destroy.Inside the child module, var.settings is therefore guaranteed to be a non-null object, while profile still receives its optional() default when omitted. Three behaviors work together here:
default— value used when the caller omits the variablenullable = false— caller cannot replace that default withnulloptional(...)— handles individual attributes within the non-null object
Root callers pass partial overrides with -var or tfvars when they need to change name or profile. See Module inputs and outputs for the full wiring pattern.
Common null mistakes
| Mistake | Why it fails or misleads | Better approach |
|---|---|---|
nullable = true assumed to create object defaults |
Only allows null as a variable value; explicit null overrides a default |
Non-null default object + nullable = false when callers must never pass null; otherwise use a conditional/guard |
optional() on inner fields with default = null parent |
Optional applies per-key on a non-null object only | Guard parent or use non-null default before .attr |
Nested optional(..., { ... }) with explicit null |
Default object replaces null — may surprise callers |
Document behavior or use conditional when null is meaningful |
| Accessing nested property before testing parent | var.config.settings.name when settings is null |
var.config.settings == null ? … : var.config.settings.name |
default = {} with required attributes |
Validate error: incompatible default | Make all fields optional() or use { name = "web" } |
Excessive try() on every expression |
Hides typos and wrong object shapes | One normalization local; conditionals for intentional absence |
Diagnostic checklist
| Step | What to run or check |
|---|---|
| Confirm null value | terraform console → var.settings or the failing expression |
| Confirm type shape | type(var.settings) — object constraint versus actual value |
| Distinguish parent null vs missing key | Non-null parent + optional() versus var.settings is null in the error |
| Check validate versus plan | Null attribute errors often appear only on terraform plan |
| Review variable default | default = null on the object that owns the attribute |
| Review module input | Child variable has nullable = false when null must not override the default |
Verify the fix
After applying a guard or default, confirm in the fix directory:
cd ~/terraform-labs/terraform-null-value-error/fixes/conditional-guardRe-run validate and plan:
terraform validate -no-color && terraform plan -no-color -input=falseSuccess! The configuration is valid.
Plan: 1 to add, 0 to change, 0 to destroy.Destroy lab resources when finished:
cd ~/terraform-labs/terraform-null-value-error/fixes/default-all-optional && terraform destroy -auto-approve -input=false 2>/dev/null || true
cd ~/terraform-labs/terraform-null-value-error/fixes/conditional-guard && terraform destroy -auto-approve -input=false 2>/dev/null || true
cd ~/terraform-labs/terraform-null-value-error/fixes/nested-guard && terraform destroy -auto-approve -input=false 2>/dev/null || true
cd ~/terraform-labs/terraform-null-value-error/fixes/try-normalize && terraform destroy -auto-approve -input=false 2>/dev/null || true
cd ~/terraform-labs/terraform-null-value-error/fixes/optional-module-input && terraform destroy -auto-approve -input=false 2>/dev/null || trueReferences
- Type constraints for objects — HashiCorp Developer
- Optional attribute modifier — HashiCorp Developer
- nullable argument for variables — HashiCorp Developer
- try function — HashiCorp Developer
Summary
Attempt to get attribute from null value means Terraform tried to read .attribute on a null parent. The error usually surfaces during terraform plan, even when terraform validate passes, because variable defaults are not always evaluated against every expression at validate time.
optional() on inner fields fixes omitted keys on a non-null object — it does not make null.name valid. When callers may pass null, use a non-null default object (with all-optional fields if you need default = {}), a conditional that tests the parent first, or a single try() in a normalization local with a documented fallback. Module inputs should default to a concrete object and set nullable = false when callers must not pass null over that default, so child code never dereferences a null parent.
Nested objects follow the same rule: test var.config.settings == null before var.config.settings.name. nullable = true only permits null as an input value; it does not supply object defaults. After refactoring, terraform plan should succeed and show the resolved argument you expect.
For optional() defaults and explicit-null behavior, continue with Optional object attributes. For when try() is appropriate versus masking errors, see Terraform try vs can.

