August 26, 2026·4 min read·engineeringterraformprovidersstate

Terraform 1.16 preserves provider decisions across planning

Terraform 1.16 returns planned private data to providers during final planning. Its new terraform_data store block also makes value retention and update policy explicit.

Terraform 1.16.0, released on August 26, preserves provider-specific planned private data across plan and apply. The release notes also introduce a store block in terraform_data that accepts ephemeral and sensitive values. Both changes concern information that must survive an operation boundary, but they give that information different meanings and different consumers.

I use Terraform as a daily driver and write providers of my own. That makes the provider change particularly interesting to me. A provider may need information from its first planning decision when Terraform asks it to produce the final plan before applying. Giving that information an explicit path through the protocol is easier to reason about than assuming the provider can independently recreate the same decision.

The configuration change raises another question. If an ephemeral input becomes a stored output, what exactly is being retained? I would want that answer before adopting the block for a sensitive value. Accepting an ephemeral input does not by itself establish that every value derived from it remains absent from persistent state.

Final planning receives the earlier private data

The provider protocol patch adds planned_private to the planning request and a client capability named store_planned_private. Its protocol comment says the client stores private data returned with an initial plan and sends it back in a subsequent planning request. The internal request comment narrows the timing to the plan immediately preceding the apply request.

This extends the information available during planning. The request already distinguishes prior private data from planned private data. Prior private data comes from the provider's last apply, while planned private data represents the earlier plan. A provider author needs to preserve that distinction because those values describe different points in the resource's lifecycle.

The implementation takes the private data from an existing planned change and puts it into the provider planning request. It also advertises the new capability. This gives a provider an explicit way to know that the client supports returning the data. I would check that capability when designing provider behavior that depends on the new contract.

The regression test demonstrates the consistency problem with a computed value. On the first plan, its test provider generates a random string and returns it as planned private data. On the later plan, the provider recovers that string from the request. The test then applies the plan and checks for errors. Its comment explains that an inconsistent planning decision would fail with an inconsistent-final-plan error.

This is a focused example of preserving a decision. It is not a benchmark or a recommendation that every computed value should be chosen randomly during planning. For a real provider, I would identify the exact information that must remain consistent and test its behavior with both an initial and a final planning call. A unit test of only the first response would leave the new request path unexamined.

The store input and output have different roles

The release-tag implementation of terraform_data defines store.input as write-only. It defines computed outputs named output and sensitive_output, with the latter marked sensitive. The sensitive argument controls which output receives the value. These declarations distinguish the incoming configuration from the value the resource retains.

A minimal non-sensitive example is:

resource "terraform_data" "example" {
  store {
    input     = "example"
    version   = 1
    sensitive = false
    replace   = false
  }
}

This uses a literal so the update policy can be examined without another resource. The implementation clears the write-only input from planned state. When an output needs a value during apply, it reads the input directly from the configuration and returns that value through the appropriate computed output. The stored output is therefore a distinct destination with its own persistence implications.

I would describe the decision to use this block as a decision to retain a value. The write-only input handling does not make the computed output write-only. Marking the selected output sensitive is also an explicit part of the schema. A reader should inspect the output behavior before assuming that an ephemeral source means the resulting stored value is temporary.

The code also handles a change in the sensitive setting by moving the value between the two output fields. That is another case I would check when exposing this resource through a module. A caller changing how the value should be classified needs to know which output to reference afterwards. I would avoid presenting both output names as interchangeable aliases in module documentation. They have different schema markings, and the implementation deliberately writes the retained value to one while clearing the other. A test can inspect that result without involving a remote provider or a service API.

Versioning controls when retention changes

The version argument provides an update trigger. The implementation compares it with the prior version when it is present. A changed version causes a stored output update. Without a version, the code compares the input directly with the existing output. This gives the author a choice between explicit version-controlled updates and updates based on the supplied value.

That choice deserves documentation in a module that wraps the resource. If the module exposes a version input, callers need to understand when to change it. Changing the source value while leaving the update version unchanged should not be assumed to refresh the stored output. I would test that case alongside a version change so the interface demonstrates both outcomes.

The replace argument determines whether a stored output change requires replacing the resource. Its value and the sensitive choice must be known during planning, as the implementation checks explicitly. These are planning decisions about the resource, even when the input value itself has different availability constraints.

For a provider author, the planned private data change offers a protocol mechanism for retaining earlier planning information. For a configuration author, store offers a resource whose retained output and update policy are visible in configuration. I would test each at its own boundary, including the final provider planning call for the first case and the resulting stored outputs for the second.