Fix Terraform "Attempt to Get Attribute from Null Value"

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:

text
var.settings.name   when var.settings is null

The 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.

NOTE
Run terraform init in each new directory before terraform validate or terraform plan.

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:

text
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.

bash
mkdir -p ~/terraform-labs/terraform-null-value-error/errors/null-parent
cd ~/terraform-labs/terraform-null-value-error/errors/null-parent

Write a nullable settings object and read .name without a guard:

bash
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
}
EOF

Initialize the working directory:

bash
terraform init -input=false
output
Terraform has been successfully initialized!

validate may still pass because it does not always evaluate every variable default against every expression:

bash
terraform validate -no-color
output
Success! The configuration is valid.

Plan evaluates var.settings.name against the default null and stops:

bash
terraform plan -no-color -input=false
output
Planning 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:

bash
echo 'var.settings' | terraform console -no-color
output
null /* 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.

bash
mkdir -p ~/terraform-labs/terraform-null-value-error/demos/omitted-vs-null
cd ~/terraform-labs/terraform-null-value-error/demos/omitted-vs-null

Define an object with one required and one optional attribute:

bash
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
}
EOF

Initialize before planning with a partial object:

bash
terraform init -input=false

Plan with only name supplied — the object exists and profile is omitted:

bash
terraform plan -no-color -input=false -var='settings={ name = "web" }'
output
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:

bash
cd ~/terraform-labs/terraform-null-value-error/errors/null-parent

Replace main.tf so profile is optional but the variable default stays null:

bash
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
}
EOF

Re-run plan — optional() never runs because there is no object to apply defaults to:

bash
terraform plan -no-color -input=false
output
Error: 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.
text
object exists with omitted optional attribute  →  parent is non-null; optional() applies
entire object = null                           →  no parent; .profile is invalid

For 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:

bash
mkdir -p ~/terraform-labs/terraform-null-value-error/errors/incompatible-default
cd ~/terraform-labs/terraform-null-value-error/errors/incompatible-default

Declare a required name with default = {}:

bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
}

variable "settings" {
  type = object({
    name    = string
    profile = optional(string, "default")
  })
  default = {}
}
EOF

Initialize and validate:

bash
terraform init -input=false

Validate catches the empty default before plan:

bash
terraform validate -no-color
output
Error: 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:

bash
mkdir -p ~/terraform-labs/terraform-null-value-error/fixes/default-all-optional
cd ~/terraform-labs/terraform-null-value-error/fixes/default-all-optional

Write an all-optional object with default = {}:

bash
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}"
}
EOF

Initialize and confirm the configuration is valid:

bash
terraform init -input=false && terraform validate -no-color
output
Success! The configuration is valid.

Plan shows both optional defaults resolved without a null parent:

bash
terraform plan -no-color -input=false
output
# 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:

bash
mkdir -p ~/terraform-labs/terraform-null-value-error/fixes/conditional-guard
cd ~/terraform-labs/terraform-null-value-error/fixes/conditional-guard

Keep default = null but branch in a local:

bash
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
}
EOF

Initialize and validate:

bash
terraform init -input=false && terraform validate -no-color
output
Success! The configuration is valid.

Plan with the default null settings object:

bash
terraform plan -no-color -input=false
output
# 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.

bash
mkdir -p ~/terraform-labs/terraform-null-value-error/errors/nested-null-no-default
cd ~/terraform-labs/terraform-null-value-error/errors/nested-null-no-default

Reference a nested .name while settings is explicitly null:

bash
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
}
EOF

Initialize the directory:

bash
terraform init -input=false

Plan surfaces the nested null parent error:

bash
terraform plan -no-color -input=false
output
Error: 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:

bash
mkdir -p ~/terraform-labs/terraform-null-value-error/fixes/nested-guard
cd ~/terraform-labs/terraform-null-value-error/fixes/nested-guard

Write a conditional that tests the nested object before reading .name:

bash
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
}
EOF

Initialize and confirm plan succeeds:

bash
terraform init -input=false && terraform plan -no-color -input=false
output
# 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:

bash
mkdir -p ~/terraform-labs/terraform-null-value-error/fixes/try-normalize
cd ~/terraform-labs/terraform-null-value-error/fixes/try-normalize

Wrap the attribute read in try() once, then reference the local elsewhere:

bash
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
}
EOF

Initialize and plan with the default null parent:

bash
terraform init -input=false && terraform plan -no-color -input=false
output
# 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 .profle instead of .profile.
  • When null is meaningful, a conditional expresses intent more clearly than try().

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.

bash
mkdir -p ~/terraform-labs/terraform-null-value-error/fixes/optional-module-input/modules/app
cd ~/terraform-labs/terraform-null-value-error/fixes/optional-module-input

Child module with optional(), a concrete default object, and nullable = false:

bash
cat > modules/app/variables.tf <<'EOF'
variable "settings" {
  type = object({
    name    = string
    profile = optional(string, "default")
  })

  nullable = false

  default = {
    name = "web"
  }
}
EOF

nullable = 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:

bash
cat > modules/app/main.tf <<'EOF'
resource "terraform_data" "svc" {
  input = "${var.settings.name}:${var.settings.profile}"
}
EOF

Root module calls the child with no arguments:

bash
cat > main.tf <<'EOF'
terraform {
  required_version = ">= 1.12.0"
}

module "app" {
  source = "./modules/app"
}
EOF

Initialize and plan:

bash
terraform init -input=false && terraform plan -no-color -input=false
output
# 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 variable
  • nullable = false — caller cannot replace that default with null
  • optional(...) — 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 consolevar.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:

bash
cd ~/terraform-labs/terraform-null-value-error/fixes/conditional-guard

Re-run validate and plan:

bash
terraform validate -no-color && terraform plan -no-color -input=false
output
Success! The configuration is valid.

Plan: 1 to add, 0 to change, 0 to destroy.

Destroy lab resources when finished:

bash
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 || true

References


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.


Frequently Asked Questions

1. What does Attempt to get attribute from null value mean in Terraform?

Terraform evaluated an expression that used dot notation on a value that is null. The parent object does not exist, so attributes such as .name or .profile cannot be read. The error usually appears during terraform plan, not validate.

2. What is the difference between a null object and a missing optional attribute?

A null object means the entire variable value is null — no keys exist. A non-null object with an omitted optional attribute still has other keys; optional() fills or nulls individual attributes. optional() on inner fields does not protect attribute access on a null parent.

3. Can I use default = {} to fix a null object variable?

Only when every attribute in the object type is optional or has its own default through optional(). If any attribute is required, default = {} fails validate with an incompatible default value error.

4. Does nullable = true prevent null attribute errors?

No. nullable = true only allows callers to pass null into the variable. It does not create object defaults or make attribute access on null safe. You still need a guard, conditional, or non-null default before you read .attribute on the value.

5. When should I use try() for null objects?

Use try() in a normalization local when external input may be null or missing fields and a documented fallback is acceptable. Do not wrap every expression in try() — that hides schema mistakes. Prefer conditionals or module defaults when absence is part of your API contract.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)