Managing infrastructure rarely ends when a resource is created. Teams may need to take a final backup, deregister an asset, or clean up an external system before infrastructure disappears. And when adopting existing infrastructure into Terraform, reusable modules have not been able to carry their own import logic: the root module had to own it.
HashiCorp Terraform 1.16 addresses both gaps. Terraform Actions can now run before and after resource destruction, and declarative `import` blocks can now live inside child modules. Together, these improvements keep more operational and adoption intent with the Terraform configuration that owns it and inside the standard plan-and-apply workflow.
»Run Actions before and after resource destruction
Some lifecycle tasks, such as taking a final database backup or removing a server from an inventory system, need to happen before or after Terraform destroys a resource. They are part of the resource lifecycle but do not correspond directly to Terraform's create, read, update, or delete operations.
Terraform Actions let providers expose imperative operations that practitioners can invoke directly or connect to a managed resource's lifecycle. Before Terraform 1.16, resource action triggers covered create and update events, but not resource destruction. Teardown tasks such as CMDB cleanup, address release, and backup verification therefore required separate automation or a manual runbook.
Terraform 1.16 adds `before_destroy` and `after_destroy` events to the `action_trigger` lifecycle block. A configuration can now place a provider-defined operation at the appropriate point in a resource's teardown:
action "example_cleanup" "archive" {
config {
resource_id = caller.id
}
}
resource "example_service" "payments" {
name = "payments"
lifecycle {
action_trigger {
events = [before_destroy]
actions = [action.example_cleanup.archive]
}
}
}
With before_destroy, a platform engineer can configure Terraform to invoke a provider-defined Action that triggers a final backup before deleting a managed database. With after_destroy, Terraform can invoke an Action to clean up an external inventory record once the resource operation completes. Terraform plans the Action with the resource operation, respects resource dependencies, and invokes it at the configured lifecycle boundary during apply. Both events can configure the Action using caller, which Terraform evaluates from the resource's state before destruction.
Destroy Actions have an important planning constraint: their configuration and trigger condition must be fully known at plan time. Terraform stores those planned values for apply, which means ephemeral values cannot be used in destroy Action configuration.
The trigger must also remain in configuration when Terraform plans the destroy. If you remove the entire resource block, you also remove its `action_trigger`, so Terraform cannot invoke the destroy Action. Plan the removal by setting the count=0 in the resource block and trigger configuration, then remove the block in a subsequent change. Refer to the destroy Action documentation for the appropriate migration pattern for your resource-addressing strategy.
Teams should also choose the Action's failure behavior deliberately. Terraform 1.16 supports `halt`, `taint`, and `continue` modes. `halt` is the default and stops dependent processing after an Action error. `continue` reports invocation errors as warnings and allows processing to continue. `taint` stops processing and marks a newly created resource for replacement; it does not make a failed destroy Action retriable.
This extends the governed Terraform workflow to more of the resource lifecycle, but it does not turn Actions into arbitrary automation. Actions remain provider-defined imperative operations, and they do not replace declarative resource management, planning, policy controls, or run approvals. For non-CRUD operations tied to a resource lifecycle, Actions provide the recommended provider-defined integration model instead of relying on provisioners, wrapper automation, or manual processes. Terraform 1.16 extends that model to destroy-time workflows with before_destroy and after_destroy.
»Declare imports inside child modules
Reusable modules are intended to encapsulate infrastructure implementation details. Import workflows have had an awkward exception: although an import block could target a resource inside a child module, the block itself still had to live in the root module.
That meant consumers adopting existing infrastructure needed to know and maintain the child module's internal resource address. A platform team updating a shared module to manage an object that already existed in every environment could not encode the complete adoption path in the module itself. Each consuming root configuration needed its own import block or state-migration procedure.
Config-driven import already made these operations part of Terraform's normal plan-and-apply workflow. Terraform 1.16 now lets module authors place the import block where the resource definition already lives: inside the child module.
variable "existing_bucket_name" {
type = string
}
resource "aws_s3_bucket" "this" {
bucket = var.existing_bucket_name
}
import {
to = aws_s3_bucket.this
id = var.existing_bucket_name
}
The calling configuration supplies the existing object's identity without duplicating the child module's internal resource address:
module "logs" {
source = "./modules/log-bucket"
existing_bucket_name = "platform-audit-logs"
}
Terraform evaluates the import in the context of each module instance, including nested modules and module calls that use count or for_each. The import remains visible in the plan, so practitioners can review the state transition and any configuration differences before apply.
This does not remove the normal responsibilities of importing infrastructure. The provider must support importing the destination resource, the object's identity must be known during planning, and the destination configuration should accurately describe the existing object. Teams should review plans carefully to avoid binding an object to the wrong resource address or unintentionally changing it after import.
The payoff is a cleaner ownership boundary. Module maintainers can encode a repeatable adoption path beside the resource they manage, while module consumers supply the existing object's identity without reproducing module-internal mappings in every root configuration.
»Additional improvements in Terraform 1.16
Terraform 1.16 also includes improvements for practitioners and tool builders:
`
terraform state show -json` and `terraform workspace list -json` provide machine-readable command output.`
terraform graph -format=mermaid` can generate Mermaid diagrams from a Terraform graph.Terraform CLI displays a summary of HCP Terraform policy evaluation outcomes for plan and apply runs.
Resource action triggers support `
on_failure` modes of `halt`, `taint`, and `continue`.Terraform is available as a pre-built binary for Linux `
s390x`.
Refer to the Terraform 1.16 changelog for the complete list of features, enhancements, bug fixes, and upgrade notes.
»Get started with Terraform 1.16
To get started:
Review the Terraform 1.16 changelog and upgrade guide.
Read the Terraform Actions and import documentation.
Try Terraform 1.16 in a non-production workflow before updating shared automation.
Terraform 1.16 includes contributions and feedback from across the Terraform community. Thank you to everyone who opened issues, described their workflows, tested prereleases, and contributed code.







