# What Is a Terraform Plan? — Actions, Replace Reasons, and the Plan JSON Format

Canonical: https://commareports.com/glossary/terraform-plan
Published: 2026-10-31

> A Terraform plan is the proposed set of changes between your configuration and real infrastructure. What create, update, replace, destroy, import and move mean, why something gets replaced, what known after apply means, and how to read terraform show -json.

# Terraform plan

**A Terraform plan is the set of changes Terraform proposes to make real
infrastructure match your configuration.** `terraform plan` refreshes what
exists, compares it with the configuration and the state file, and gives each
resource an action. Nothing changes until `terraform apply` runs that plan,
which is why the plan is the thing a reviewer actually approves.

## The actions

| Action        | Human output | Plan JSON `actions`              | Means                                                             |
| ------------- | ------------ | -------------------------------- | ----------------------------------------------------------------- |
| Create        | `+`          | `["create"]`                     | A new object will be made                                         |
| Update        | `~`          | `["update"]`                     | Changed in place; the object keeps its identity                   |
| Replace       | `-/+`        | `["delete","create"]`            | Destroyed, then a new one created                                 |
| Replace (CBD) | `+/-`        | `["create","delete"]`            | New one created first (`create_before_destroy`), then old removed |
| Destroy       | `-`          | `["delete"]`                     | The object will be deleted                                        |
| Read          | `<=`         | `["read"]`                       | A data source is read during apply, not during plan               |
| Import        | —            | `["no-op"]` + `importing`        | An existing object is brought under management (`import` block)   |
| Move          | —            | `["no-op"]` + `previous_address` | Same object, new address (`moved` block)                          |
| Forget        | —            | `["forget"]`                     | Removed from state, real object left alone (`removed` block)      |

Moves and imports can also carry an update. The JSON then says `["update"]`
with a `previous_address` or `importing` alongside.

## Why something gets replaced

Replacement is the action reviewers care most about. A replaced database or
load balancer is an outage, not a tweak. Terraform records why in
`action_reason`:

| `action_reason`                     | Cause                                                   |
| ----------------------------------- | ------------------------------------------------------- |
| `replace_because_cannot_update`     | A changed attribute is immutable for this resource type |
| `replace_because_tainted`           | The object was marked tainted (often a failed create)   |
| `replace_by_request`                | `terraform plan -replace=ADDRESS`                       |
| `replace_by_triggers`               | `lifecycle { replace_triggered_by = [...] }` fired      |
| `delete_because_no_resource_config` | The resource block was deleted from the configuration   |
| `delete_because_each_key`           | The `for_each` key no longer exists                     |
| `delete_because_count_index`        | The `count` shrank                                      |

For `cannot_update`, the attribute responsible is listed in `replace_paths`.
The human output marks it `# forces replacement`. A changed AMI on an
instance or a renamed RDS identifier are the classic cases.

## Known after apply and sensitive values

Some values don't exist until the change is made: an id the cloud assigns,
an IP, anything computed from a resource that isn't created yet. The plan
shows these as `(known after apply)`. In JSON they are `true` at the same path
in `after_unknown`. The `after` value is null or missing there.

Sensitive values (a `sensitive = true` variable or output, or an attribute the
provider marks sensitive) show as `(sensitive value)`. The JSON flags them in
`before_sensitive` / `after_sensitive`, **but the real values are still in the
file**. A plan file is a secret. Don't attach `plan.json` to a ticket. Share a
rendering that masks them.

## The plan JSON

```bash
terraform plan -out=tfplan
terraform show -json tfplan > plan.json
```

The top-level fields you'll use:

- `resource_changes`: one entry per resource instance, with `address`,
  `type`, `provider_name`, `change.actions`, `change.before`, `change.after`,
  the unknown and sensitive masks, `replace_paths`, and `action_reason`.
- `output_changes`: the same before/after shape for each root output.
- `resource_drift`: objects that changed outside Terraform since the last
  apply. These are refreshed into state, not reverted, unless the config
  also changes them.
- `planned_values` / `prior_state`: full snapshots, useful for policy tools
  like OPA or Sentinel.
- `errored`: true if planning failed partway, so the plan is incomplete.

`terraform plan -json` is a different thing: a stream of one JSON object per
line describing UI events (`planned_change`, `change_summary`, `diagnostic`).
It names each resource's action but carries no attribute values.

## Reviewing a plan

The plan text works for whoever wrote the change. It works badly for everyone
else who has to approve it. It's thousands of lines in a CI log. It gets cut
off in a PR comment past GitHub's 65,536-character limit. And it mixes the one
destroy that matters in with two hundred tag updates. A review-ready plan puts
destroys and replaces first, says why each one happens, shows only the
attributes that change, and masks secrets. It needs to sit somewhere a
reviewer without cloud credentials can open it, comment on one resource, and
sign off.

## Try it

Comma is free: unlimited reports, unlimited commenters, unlimited revision
history.

**[Open the Terraform plan viewer →](/tools/terraform-plan-viewer)**

### Related

- [Terraform plan viewer](/tools/terraform-plan-viewer) · [JSON diff](/tools/json-diff) · [JSON viewer](/tools/json-viewer)
- [Share a Terraform plan](/share-terraform-plan) · [Share a Pulumi preview](/share-pulumi-preview) · [Share a Checkov report](/share-checkov-report)
- [Approve an HTML report](/approve-html-report) · [SARIF](/glossary/sarif) · [Glossary](/glossary)
