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

As Terraform's built-in successor to the hashicorp/null provider's null_resource, terraform_data (v1.4+) offers key advantages: it requires no external provider, its triggers_replace argument supports all value types (unlike null_resource's string-only triggers), and it provides explicit input/output attributes for data handling. It is the recommended choice for new configurations.

Understanding Arguments and Attributes

terraform_data uses key arguments: input (optional) stores arbitrary data, causing an update if its value changes, and triggers_replace (optional) forces resource replacement if its value changes. Read-only attributes include output (reflecting input's value) and id (unique resource identifier). The choice between input and triggers_replace dictates 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)

Key Use Cases

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.

Advanced Triggering and Dependency Management

The triggers_replace argument is central for advanced scenarios, forcing resource replacement when its computed value (any data type: string, list, map, filemd5(), timestamp()) changes from the previous state. This allows precise triggering based on diverse conditions. A common challenge involves boolean flags: triggers_replace reacts to a boolean's change (e.g., false to true or true to false), not its static true state. The idiomatic solution is often conditional resource creation using for_each (e.g., for_each = var.enable_feature ? toset(["active"]) : toset()).

Working with Provisioners

terraform_data often hosts create and destroy provisioners when no other logical resource fits. Within provisioners, the self object provides access to input, output, and triggers_replace values, crucial for context-aware actions, especially stateful cleanup in destroy provisioners. However, provisioners are a last resort. Prefer native Terraform resources, data sources, cloud-init, or dedicated configuration management tools. Using provisioners means the user is responsible for script idempotency and any state managed outside Terraform.

Best Practices and Considerations

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.

Limitations and Common Pitfalls

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.

Migrating from null_resource

Migrating from null_resource to terraform_data (recommended for Terraform v1.4+) is streamlined using a moved block. This allows changing the resource type and renaming triggers to triggers_replace while preserving state history. After defining the terraform_data resource and the moved block, terraform plan should show no net changes, confirming a successful state migration path. The moved block can be removed after applying the changes.

Conclusion and Outlook

terraform_data is a handy built-in for holding arbitrary data, triggering actions, and standing in for null_resource. It pulls common patterns into Terraform itself, so you lean on fewer external pieces. Use it with some restraint. Reach for native resources and data sources first, keep your provisioner scripts idempotent and free of hardcoded secrets, and don't let one terraform_data block do too many jobs at once. It covers a lot of ground today, and 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.