TrademarkTrademark
Features
Documentation

A Concise Guide to terraform_data Resource

Learn how terraform_data stores JSON, passes values between modules, and streamlines complex workflows with step-by-step examples and best practices.
Sebastian StadilAugust 7, 2025Updated March 31, 2026
A Concise Guide to terraform_data Resource
Key takeaways
  • The terraform_data resource (Terraform v1.4+) is a built-in container for managing arbitrary data and hosting provisioners without creating external infrastructure.
  • terraform_data is the recommended successor to null_resource because it needs no external provider and its triggers_replace argument supports all value types, not just strings.
  • Use input to store data with an in-place update, and triggers_replace to force resource replacement when its value changes.
  • triggers_replace reacts to a value changing, not a static state, so boolean flags are better handled with conditional for_each.
  • Migrate from null_resource using a moved block, which preserves state history so terraform plan shows no net changes.

The terraform_data resource (Terraform v1.4+) gives you a place to keep arbitrary data, trigger actions, and run provisioners without standing up any real infrastructure. It ships with Terraform, so there's no provider to install. People reach for it to store lifecycle-managed values, to host provisioners when nothing else fits, and as the modern stand-in for null_resource. This guide walks through what it does, how to use it sensibly, and how to migrate off null_resource.

terraform_data vs. null_resource

terraform_data (v1.4+) is Terraform's built-in replacement for the hashicorp/null provider's null_resource. It needs no external provider, its triggers_replace argument takes any value type (where null_resource's triggers only took strings), and it gives you explicit input and output attributes for handling data. For new configurations, it's the one to use.

What Arguments and Attributes Does terraform_data Have?

terraform_data has two main arguments. input (optional) stores arbitrary data, and changing its value plans an in-place update. triggers_replace (optional) forces a replacement when its value changes. There are also two read-only attributes: output, which mirrors input's value, and id, the unique resource identifier. Whether you reach for input or triggers_replace decides whether downstream actions see an update or a full replacement.

Name Type Description Behavior on Change
input Argument (Optional) Arbitrary data stored in state, reflected in output. Plans an in-place update for terraform_data.
triggers_replace Argument (Optional) Arbitrary data whose change forces resource replacement. Forces replacement of terraform_data.
output Attribute Value of the input argument. Changes if input changes.
id Attribute Unique resource identifier. N/A (assigned by Terraform)

When Should You Use terraform_data?

terraform_data is useful for:

  • Storing and referencing lifecycle-managed arbitrary data (e.g., consolidating configuration details).
  • Simulating resource dependencies or creating explicit data flows.
  • Triggering replacement of other resources via lifecycle.replace_triggered_by (e.g., redeploying services based on a version change).
  • Performing conditional checks or actions using provisioners (e.g., verifying external resource states), noting that provisioner-managed resources are outside Terraform state.
  • Creating multiple instances with for_each for dynamic data handling or actions. While versatile for decoupling logic, avoid over-reliance to maintain clarity.

How Does triggers_replace Handle Complex Triggering and Dependencies?

triggers_replace does the heavy lifting in trickier setups. It forces a replacement when its computed value changes from the previous state, and that value can be any type: a string, list, map, filemd5(), timestamp(), and so on. That lets you trigger on all sorts of conditions. Boolean flags are where people get tripped up. triggers_replace reacts to a boolean changing (false to true, or true to false), not to its sitting at true. The usual fix is to create the resource conditionally with for_each (e.g., for_each = var.enable_feature ? toset(["active"]) : toset()).

How Do You Host Provisioners on terraform_data?

terraform_data often hosts create and destroy provisioners when no other resource is a sensible home for them. Inside a provisioner, the self object gives you the input, output, and triggers_replace values, which you need for context-aware actions and especially for stateful cleanup in destroy provisioners. That said, treat provisioners as a last resort. Reach for native Terraform resources, data sources, cloud-init, or a real configuration management tool first. Once you use a provisioner, keeping the script idempotent and tracking any state outside Terraform is on you.

What Are the Best Practices for Using terraform_data?

Effective use of terraform_data involves:

  • Prioritizing native Terraform resources/data sources over provisioners.
  • Ensuring provisioner scripts are idempotent and handle errors gracefully.
  • Keeping logic focused and clear within each terraform_data instance.
  • Securing provisioners by avoiding hardcoded credentials and sanitizing inputs.
  • Understanding that terraform_data is a utility for ancillary tasks, and that management of any state altered by its provisioners falls to the user.

What Limitations and Pitfalls Should You Watch For With terraform_data?

Be aware of:

  • Resources altered by provisioners are not tracked in Terraform state, potentially creating "shadow infrastructure."
  • local-exec provisioners create dependencies on the execution environment's tools.
  • Overuse can lead to complex, opaque configurations.
  • Misunderstanding triggers_replace behavior with boolean flags (reacts to change, not absolute value).
  • Large input values can bloat the state file.

How Do You Migrate from null_resource to terraform_data?

A moved block makes migrating from null_resource to terraform_data (recommended on Terraform v1.4+) easy. It lets you change the resource type and rename triggers to triggers_replace while keeping the state history. Once you've defined the terraform_data resource and the moved block, terraform plan should show no net changes, which tells you the state migrated cleanly. You can drop the moved block after you apply.

So When Should You Actually Reach for terraform_data?

terraform_data is a built-in for holding arbitrary data, triggering actions, and standing in for null_resource. Because it pulls these patterns into Terraform itself, you depend on fewer external pieces. Reach for native resources and data sources first. When you do use it, keep your provisioner scripts idempotent and free of hardcoded secrets, and don't let one terraform_data block do too many jobs at once. Later Terraform releases may smooth out the rougher edges around complex triggering logic.

Code editor displaying syntax-highlighted code on a dark screen

About the author
Sebastian StadilCEO at Scalr
Sebastian Stadil is the CEO of Scalr with 15+ years of DevOps experience. He started with AWS in 2004 and advised early Microsoft Azure and Google Cloud.