Terraform has been iterating a lot more in the past few months introducing features or updates to the core binary which were always desired. Looking at the release notes of v1.15.0-alpha20260204 version , you see a few gems.

  • You can set a deprecated attribute on variable and output blocks to indicate that they are deprecated. This will produce warnings when passing in a value for a deprecated variable or when referencing a deprecated output.. Its been a wish to have an easy way to manage guidance around input and output deprecation between releases of a module than just via release notes.
  • Terraform Test: Allow functions within mock blocks

I will focus on the first one for this post. I have a few collegues who are pretty pumped about this as they manage public and private modules.

Note: This post covers features from Terraform 1.15.0-alpha20260204. As this is an alpha release, syntax, behavior, and functionality may change before the final release. I wouldn’t reccommend using this in a production setup as is.

Why is this important ?

Module authors:

I have seen many a teams end up passing the entire resource objects than specific attributes from a module as output. I feel that is lazy, but I can understand why they had to do what they had to because of past experiences when the interface of their moodule changed due to some suboptimal initial decisions. Every one has good intentions and want to fix them as soon as possible. But what about the teams who use their module ?

Module users:

When a module interfaces change, you’re left scrambling through release notes trying to figure out what broke and how to fix it. A module that worked yesterday suddenly requires different inputs, or worse - outputs you depend on have changed. Without clear deprecation warnings , you only discover these issues when terraform plan fails or produces unexpected results way later in a future release.

Deprecation-Guided Migration

The deprecation feature in 1.15-alpha allows module authors to guide users through interface changes gradually. Here’s how it works:

Let’s establish the Terraform version I am using currently.

$ terraform version
Terraform v1.15.0-alpha20260204
on darwin_arm64
+ provider registry.terraform.io/hashicorp/aws v6.31.0

Variable Deprecation

Simple message

Lets start with a simple example. No providers. No modules. Just one variable.

# main.tf
variable "ref_id" {
  type        = string
  deprecated  = "This variable will be removed in v2.0"
  description = "Reference id for service 1"
}

Run Terraform plan :

terraform plan
var.ref_id
  Reference id for service 1

  Enter a value: 234dfeff-123213dsad-12dsad


No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no
differences, so no changes are needed.
│ Warning: Deprecated variable got a value
│   on main.tf line 3, in variable "ref_id":
│    1: variable "ref_id" {
│    2:   type        = string
│    3:   deprecated  = "This variable will be removed in v2.0"
│ This variable will be removed in v2.0

This looks awfully similar to the provider attribute deprecations you may have seen between major versions, the .name vs .region one for aws_region datasource for example.

The only thing with this example is that when you run terraform validate with a default value set for the var, it doesn’t show any deprecation messages.

variable "ref_id" {
  type        = string
  default     = "1213123"
  deprecated  = "This variable will be removed in v2.0"
  description = "Reference id for service 1"
}

On Validate

tf validate
Success! The configuration is valid.

Structured message

variable "ref_id" {
  type        = string
  deprecated  = <<-EOT
       This variable is deprecated and will be removed in v2.0.

      Migration steps:
      1. Replace with 'ref_name' for the primary value
      2. See release notes here: https://example.com
    EOT
  description = "Reference id for service 1"
}

Terraform plan gives you :


terraform plan
var.ref_id
  Reference id for service 1

  Enter a value: 234dfeff-123213dsad-12dsad

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no
differences, so no changes are needed.
│ Warning: Deprecated variable got a value
│   on main.tf line 3, in variable "ref_id":
│    1: variable "ref_id" {
│    2:   type        = string
│    3:   deprecated  = <<-EOT
│    4:       ⚠️ This variable is deprecated and will be removed in v2.0.
│    5:       Migration steps:
│    6:       1. Replace with 'ref_name' for the primary value
│    7:       2. See release notes here: https://example.com
│    8:     EOT
│ ⚠️ This variable is deprecated and will be removed in v2.0.
│ Migration steps:
│ 1. Replace with 'ref_name' for the primary value
│ 2. See release notes here: https://example.com

Function support ?

Let’s test this. OK. I know I am pushing the example here :) But let’s see how the feature behaves.

variable "ref_id" {
  type        = string
  deprecated  = title("This variable is deprecated and will be removed in v2.0.")
  description = "Reference id for service 1"
}

Terraform plan/validate :

terraform plan
│ Error: Unsuitable value type
│   on main.tf line 3, in variable "ref_id":
│    3:   deprecated  = title("This variable is deprecated and will be removed in v2.0.")
│ Unsuitable value: value must be known
│ Error: Function calls not allowed
│   on main.tf line 3, in variable "ref_id":
│    3:   deprecated  = title("This variable is deprecated and will be removed in v2.0.")
│ Functions may not be called here.

Functions are not supported. But what about the Unsuitable value: value must be known message.

variable "ref_id" {
  type        = string
  deprecated  = null
  description = "Reference id for service 1"
}

Aaah, the use of a function left the deprecated value to be null. Supported by the result below.

terraform plan
│ Error: Unsuitable value type
│   on main.tf line 3, in variable "ref_id":
│    3:   deprecated  = null
│ Unsuitable value: null value is not allowed

Empty String:

variable "ref_id" {
  type        = string
  deprecated  = "" # empty string is supported
  description = "Reference id for service 1"
}

Empty string result:


terraform plan
var.ref_id
  Reference id for service 1

  Enter a value: 234dfeff-123213dsad-12dsad

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no
differences, so no changes are needed.
│ Warning: Deprecated variable got a value
│   on main.tf line 3, in variable "ref_id":
│    1: variable "ref_id" {
│    2:   type        = string
│    3:   deprecated  = ""

So how have you handled deprecations of inputs in modules before ?

Module example

The usage doesn’t change much for variables from within a module.

# modules/app/main.tf
variable "ref_id" {
  type        = string
  default     = null
  deprecated  = "⚠️ Pass 'ref_name' instead of the variable. This will be removed in v2.0"
  description = "Reference id for service 1"
}

variable "ref_name" {
  type        = string
  default     = null
  description = "Reference name for service 1"
}

# Handle both old and new patterns during transition
locals {
  actual_ref = var.ref_id != null ? var.ref_id : var.ref_name
}


# main.tf
module "app_with_deprecations" {
  source = "./modules/app"
  ref_id = "234dfeff-123213dsad-12dsad"
}

On Terraform plan:

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no
differences, so no changes are needed.
│ Warning: Deprecated variable got a value
│   on main.tf line 3, in module "app_with_deprecations":
│    3:   ref_id = "234dfeff-123213dsad-12dsad"
│ ⚠️ Pass 'ref_name' instead of the variable. This will be removed in v2.0

Outputs

Lets switch to how the outputs behave in this case.

Module outputs

# modules/app/main.tf
variable "ref_id" {
  type        = string
  default     = null
  deprecated  = "⚠️ Pass 'ref_name' instead of the variable. This will be removed in v2.0"
  description = "Reference id for service 1"
}

variable "ref_name" {
  type        = string
  default     = null
  description = "Reference name for service 1"
}

...

output "old_value" {
  value      = var.ref_id
  deprecated = "⚠️ Pass 'name_value' instead of the output. This will be removed in v2.0"
}

output "new_value" {
  value = var.ref_name
}

Terraform plan or apply:

You can apply this plan to save these new output values to the Terraform state, without changing
any real infrastructure.
│ Warning: Deprecated variable got a value
│   on main.tf line 3, in module "app_with_deprecations":
│    3:   ref_id = "234dfeff-123213dsad-12dsad"
│ ⚠️ Pass 'ref_name' instead of the variable. This will be removed in v2.0
(and 2 more similar warnings elsewhere)
│ Warning: Deprecated value used
│   on main.tf line 7, in output "mod_out":
│    7:   value = module.app_with_deprecations.old_value
│   The deprecation originates from module.app_with_deprecations.old_value
│ ⚠️ Pass 'name_value' instead of the output. This will be removed in v2.0
(and one more similar warning elsewhere)

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

mod_out = "234dfeff-123213dsad-12dsad"

What about root module outputs ?

# main.tf
output "mod_out" {
  value      = "Deprecated value"
  deprecated = "This is not supported :) "
}

Terraform plan or apply:

│ Error: Root module output deprecated
│   on main.tf line 3, in output "mod_out":
│    3:   deprecated = "This is not supported :) "
│ Root module outputs cannot be deprecated, as there is no higher-level module to inform of the
│ deprecation.

Only module outputs can be deprecated, not root module outputs. And as the message indicates, it makes sense as there is no higher level module which should know.

Deprecation Suppression flag

I was a little confused at first on how this would work. It was a little bit of trial and error to see how the suppression works. And I did keep trying with the wrong flag too ( suppress_deprecations_warnigns based on the PR ) In short :

The ignore_nested_deprecations attribute lets you suppress these internal warnings while still being alerted when you use deprecated outputs from the module.

Understanding Internal vs Your Warnings

Let’s create a module structure to demonstrate:

# modules/legacy/nested/main.tf
output "deprecated_output" {
  deprecated = "This nested output is deprecated"
  value      = "nested-value"
}
# modules/legacy/main.tf
output "old_api" {
  deprecated = "Use new_api instead"
  value      = "legacy-value"
}

output "new_api" {
  value = "new-value"
}

module "nested" {
  source = "./nested"
}

# This usage inside the module generates an "internal" warning
locals {
  internal_use = module.nested.deprecated_output
}

Lets test this across 4 scenarios

I am including both the responses from terraform validate and terraform plan on all these scenarios as the latter did show me the additional collapsed warning messages which I believe are due to my usage of the module output.

Scenario 1: No suppression, output defined

# main.tf
module "with_warnings" {
  source = "./modules/legacy"
}

output "test_output" {
  value = module.with_warnings.old_api
}
tf validate
│ Warning: Deprecated value used
│   on main.tf line 10, in output "test_output":
│   10:   value = module.with_warnings.old_api
│   The deprecation originates from module.with_warnings.old_api
│ Use new_api instead
(and one more similar warning elsewhere)
Success! The configuration is valid, but there were some validation warnings as shown above.


experiments/deprecation_notice/test-example via 💠 default
❯ tf plan

Changes to Outputs:
  + test_output = "legacy-value"

You can apply this plan to save these new output values to the Terraform state, without changing any real
infrastructure.
│ Warning: Deprecated value used
│   on main.tf line 10, in output "test_output":
│   10:   value = module.with_warnings.old_api
│   The deprecation originates from module.with_warnings.old_api
│ Use new_api instead
(and 3 more similar warnings elsewhere)

──────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these
actions if you run "terraform apply" now.

Result: validate shows 2 warnings - your usage of deprecated output + internal module usage while plan shows 4 in total.

Scenario 2: No suppression, output commented

# main.tf
module "with_warnings" {
  source = "./modules/legacy"
}

# output "test_output" {
#   value = module.with_warnings.old_api
# }
tf validate
│ Warning: Deprecated value used
│   on modules/legacy/main.tf line 16, in locals:
│   16:   internal_use = module.nested.deprecated_output
│   The deprecation originates from module.nested.deprecated_output
│ This nested output is deprecated
Success! The configuration is valid, but there were some validation warnings as shown above.


experiments/deprecation_notice/test-example via 💠 default
❯ tf plan

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no
changes are needed.
│ Warning: Deprecated value used
│   on modules/legacy/main.tf line 16, in locals:
│   16:   internal_use = module.nested.deprecated_output
│   The deprecation originates from module.nested.deprecated_output
│ This nested output is deprecated
(and one more similar warning elsewhere)

Result: validate shows 1 warning - only the internal module’s use of nested deprecated output. Plan shows 2.

Scenario 3: Suppression enabled, output commented

# main.tf
module "suppressed" {
  source                     = "./modules/legacy"
  ignore_nested_deprecations = true
}

# output "test_output" {
#   value = module.suppressed.old_api
# }
tf validate
Success! The configuration is valid.


experiments/deprecation_notice/test-example via 💠 default
❯ tf plan

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no
changes are needed.

Result: Shows 0 warnings - internal deprecation suppressed, no root usage

Scenario 4: Suppression enabled, output defined

# main.tf
module "suppressed" {
  source                     = "./modules/legacy"
  ignore_nested_deprecations = true
}

output "test_output" {
  value = module.suppressed.old_api
}
tf validate
│ Warning: Deprecated value used
│   on main.tf line 11, in output "test_output":
│   11:   value = module.suppressed.old_api
│   The deprecation originates from module.suppressed.old_api
│ Use new_api instead
Success! The configuration is valid, but there were some validation warnings as shown above.


experiments/deprecation_notice/test-example via 💠 default
❯ tf plan

Changes to Outputs:
  + test_output = "legacy-value"

You can apply this plan to save these new output values to the Terraform state, without changing any real
infrastructure.
│ Warning: Deprecated value used
│   on main.tf line 11, in output "test_output":
│   11:   value = module.suppressed.old_api
│   The deprecation originates from module.suppressed.old_api
│ Use new_api instead
(and one more similar warning elsewhere)

Result: validate shows 1 warning - your usage still warned, internal deprecation suppressed. Plan shows 2 as it was previously.

I think it would come in handy if I am using third party modules with deprecations I can’t fix or has changes which I don’t technically use or care about in my usage. At the same time , I want deprecations in my own module to be surfaced to the users. Hopefully that helps to understand how the ignore_nested_deprecations functions.

Conclusion

Terraform 1.15’s deprecation feature addresses a pain point in module upgrades and management. It provides module authors with the tools they need to improve their interfaces while maintaining backward compatibility and giving users clear guidance on module usage.