| Tested on | Ubuntu 26.04 LTS (Resolute Raccoon) |
|---|---|
| Package | terraform 1.15.8-1hashicorp/random 3.6.3hashicorp/local 2.5.2 |
| Applies to | Any host with Terraform installed |
| Lab environment | Single Ubuntu VM with Terraform — Terraform lab environment on Ubuntu |
| Privilege | Normal user |
| Scope | Understanding Terraform known after apply — normal computed unknowns in plan and output, when unknown values break count and for_each instance identity, static map keys with apply-time values, deferred data source reads, module output propagation, terraform console diagnostics, and when not to force plan-time knowledge. Does not cover the full expressions catalog or every provider computed attribute. |
| Related guides | Terraform expressions count and for_each Invalid for_each argument fixes terraform console terraform plan |
(known after apply) in a plan means Terraform knows an expression is valid but cannot know its value until a provider operation runs. The value is unknown at plan time — not missing, not an error by itself.
resource creates → provider returns id, arn, hex, …
expression references that attribute → plan shows (known after apply)
apply completes → value is concrete in state and outputsEach scenario uses its own directory under ~/terraform-labs/terraform-known-after-apply/. Examples use terraform_data, hashicorp/random, and hashicorp/local.
What known after apply means
On a first-time plan, computed resource attributes do not exist in state yet. Terraform marks them unknown instead of inventing a placeholder.
mkdir -p ~/terraform-labs/terraform-known-after-apply/demos/normal-computed
cd ~/terraform-labs/terraform-known-after-apply/demos/normal-computedWrite a seed resource and outputs that reference its computed fields:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
}
resource "terraform_data" "seed" {
input = "seed"
}
locals {
derived = terraform_data.seed.output
}
output "seed_id" {
value = terraform_data.seed.id
}
output "seed_output" {
value = terraform_data.seed.output
}
output "derived" {
value = local.derived
}
EOFInitialize and plan without applying:
terraform init -input=falsePlan before the first apply to see computed fields marked unknown:
terraform plan -no-color -input=false# terraform_data.seed will be created
+ resource "terraform_data" "seed" {
+ id = (known after apply)
+ input = "seed"
+ output = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ derived = (known after apply)
+ seed_id = (known after apply)
+ seed_output = (known after apply)id, output, and local.derived all depend on the resource existing. Plan exits successfully — unknown values in the diff are expected before the first apply.
When unknown values are normal
Unknown values are safe when they affect argument values or outputs but not how many instances Terraform must create.
| Safe at plan time | Must be known at plan time |
|---|---|
| Resource argument set from another resource's computed attribute | count value |
| Root or child module output referencing computed values | for_each keys or set members |
local or output expressions chaining computed attributes |
Resource addresses derived from unknown keys |
After apply, run terraform plan again — the same attributes show concrete values and the output block updates.
When unknown values cause errors
Terraform must decide instance identity during planning for count and for_each. If keys or the count number depend on values known only after apply, plan fails — Terraform cannot build the instance map.
count has the same rule: the count expression must be known during plan. The lab focuses on for_each because that is the usual search path.
mkdir -p ~/terraform-labs/terraform-known-after-apply/errors/for-each-unknown-keys
cd ~/terraform-labs/terraform-known-after-apply/errors/for-each-unknown-keysDrive for_each from a random_id hex string — the set member stays unknown until apply:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
resource "random_id" "seed" {
byte_length = 4
}
resource "terraform_data" "svc" {
for_each = toset([random_id.seed.hex])
input = each.value
}
EOFInitialize, then plan to surface the structural error:
terraform init -input=falsePlan should fail because for_each set members stay unknown:
terraform plan -no-color -input=falseError: Invalid for_each argument
on main.tf line 16, in resource "terraform_data" "svc":
16: for_each = toset([random_id.seed.hex])
├────────────────
│ random_id.seed.hex is a string, known only after apply
The "for_each" set includes values derived from resource attributes that
cannot be determined until apply, and so Terraform cannot determine the full
set of keys that will identify the instances of this resource.
When working with unknown values in for_each, it's better to use a map value
where the keys are defined statically in your configuration and where only
the values contain apply-time results.The error names the unknown attribute and suggests the static-key map pattern. Full fix walkthrough: Invalid for_each argument fixes.
Fix for_each keys known only after apply
Keep keys in configuration; let values stay unknown until apply.
mkdir -p ~/terraform-labs/terraform-known-after-apply/fixes/for-each-static-keys
cd ~/terraform-labs/terraform-known-after-apply/fixes/for-each-static-keysUse a map with a literal key and a computed value:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
resource "random_id" "seed" {
byte_length = 4
}
resource "terraform_data" "svc" {
for_each = {
app = random_id.seed.hex
}
input = each.value
}
output "instances" {
value = {
for k, v in terraform_data.svc : k => v.output
}
}
EOFPlan succeeds — instance app is identified even though random_id.seed.hex is still unknown:
terraform init -input=falseStatic map keys let plan succeed while argument values remain unknown:
terraform plan -no-color -input=false# terraform_data.svc["app"] will be created
+ resource "terraform_data" "svc" {
+ id = (known after apply)
+ input = (known after apply)
+ output = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ instances = {
+ app = (known after apply)
}The key app is known; only input and the output map value remain unknown. That is the intended split for count and for_each with provider-computed data.
Data sources deferred until apply
Data sources normally read during plan. When a data source depends on a resource that is still being created, Terraform defers the read until apply.
mkdir -p ~/terraform-labs/terraform-known-after-apply/demos/deferred-data
cd ~/terraform-labs/terraform-known-after-apply/demos/deferred-dataA local_sensitive_file writes content from random_id; the local_file data source waits on that resource:
cat > main.tf <<'EOF'
terraform {
required_version = ">= 1.12.0"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
resource "random_id" "seed" {
byte_length = 4
}
resource "local_sensitive_file" "secret" {
content = random_id.seed.hex
filename = "${path.module}/.secret"
}
data "local_file" "secret" {
filename = local_sensitive_file.secret.filename
depends_on = [local_sensitive_file.secret]
}
output "data_content" {
value = data.local_file.secret.content
}
EOFPlan marks the data read as deferred:
terraform init -input=falsePlan marks the data source read as deferred until apply:
terraform plan -no-color -input=false# data.local_file.secret will be read during apply
# (depends on a resource or a module with changes pending)
<= data "local_file" "secret" {
+ content = (known after apply)
+ filename = "./.secret"
+ id = (known after apply)
}
# local_sensitive_file.secret will be created
+ resource "local_sensitive_file" "secret" {
+ content = (sensitive value)
...
}
Changes to Outputs:
+ data_content = (known after apply)The <= prefix means read during apply, not an error. After apply, the data source content is known on the next plan.
Outputs and module propagation
Child module outputs that reference computed resources stay unknown at the root until apply — that is normal propagation, not a module bug.
mkdir -p ~/terraform-labs/terraform-known-after-apply/demos/module-output/modules/child
cd ~/terraform-labs/terraform-known-after-apply/demos/module-outputChild module exposes a computed output:
cat > modules/child/main.tf <<'EOF'
resource "terraform_data" "marker" {
input = "from-child"
}
output "result" {
value = terraform_data.marker.output
}
EOFRoot reads it:
cat > main.tf <<'EOF'
module "child" {
source = "./modules/child"
}
output "from_module" {
value = module.child.result
}
EOFInitialize the module tree, then plan from the root:
terraform init -input=falseRoot plan propagates unknown through the child module output:
terraform plan -no-color -input=false# module.child.terraform_data.marker will be created
+ resource "terraform_data" "marker" {
+ id = (known after apply)
+ output = (known after apply)
}
Changes to Outputs:
+ from_module = (known after apply)Root outputs cannot be more specific than the child output they reference. See Terraform module input and output for wiring patterns.
Diagnose unknown values
Use terraform console to inspect an expression without applying. Reuse the demos/normal-computed/ configuration from earlier in this article:
cd ~/terraform-labs/terraform-known-after-apply/demos/normal-computedInitialize, then ask console for the seed resource output:
terraform init -input=falsePipe the seed output expression into console:
echo 'terraform_data.seed.output' | terraform console(known after apply)The chained local should show the same unknown:
echo 'local.derived' | terraform console(known after apply)Console confirms the dependency chain. Pair with terraform plan to see which resources and outputs carry unknowns in the diff. For graph-level dependencies, see terraform graph.
Do not fix normal unknowns
You do not need to eliminate every (known after apply) line from a plan.
- Provider-computed IDs, ARNs, and checksums are unknown until create — that is by design
- Outputs that surface those values will stay unknown on first plan
- Unknown values inside a
for_eachmap are fine when keys are static
Avoid workarounds that hide unknowns without fixing structure:
-targetapply only to "unblock"for_eachkeys — use for one-off debugging, not as permanent designtry()orcoalesce()around unknown keys — does not make keys known at plan time- Replacing
for_eachwithcounton an unknown length —counthas the same plan-time requirement
Fix structural errors by redesigning keys and instance counts. Accept computed unknowns everywhere else.
Diagnostic checklist
| Symptom | Likely cause | Action |
|---|---|---|
(known after apply) on resource id or computed arg |
First plan before create | Normal — apply, then re-plan |
Invalid for_each argument + known only after apply |
Dynamic keys from computed attributes | Static map keys; unknown values only |
count value not known |
Count depends on computed attribute | Rework to known count or use static for_each keys |
<= data … read during apply |
Data source depends on pending resource | Normal on first create; verify depends_on |
| Root output unknown, child resource unknown | Module output propagation | Normal until child resource exists |
| Unknown unexpectedly persists after resources already exist | Dependency, provider-computed value, or refresh/state issue | Run a normal terraform plan; use terraform plan -refresh-only when specifically checking state drift |
References
- Values — types and values — HashiCorp Developer
- for_each meta-argument — HashiCorp Developer
- count meta-argument — HashiCorp Developer
- Data sources — HashiCorp Developer
Summary
(known after apply) means Terraform validly references a value that does not exist in state until apply. On a first plan, computed resource attributes, chained locals, outputs, and module outputs commonly show unknown — that is expected, not a failure.
Planning breaks when unknown values drive structure: for_each keys, set members, or count must be known early so Terraform can name instances. The fix is static keys with apply-time values, not forcing every provider attribute to appear in the plan diff.
Data sources that depend on resources still being created read during apply, marked with <= in the plan. Console and plan together show which expressions stay unknown and whether the issue is cosmetic or structural.
For expression syntax around unknowns, continue with Terraform expressions. When plan fails on for_each specifically, use Invalid for_each argument fixes.

