Free tool

A Terraform plan someone can approve.

Paste the JSON from terraform show -json tfplan. See what gets destroyed or replaced first, and why. Then every update and create, with a before/after table for each resource. Copy a summary for the pull request, or send the whole plan to a reviewer who can’t run Terraform.

plan.json
8.2 KBshow -json · Terraform 1.9.8
Terraform plan.html

Plan: 1 to import, 3 to add, 2 to change, 2 to destroy. 2 destroyed or replaced. 1 changed outside Terraform.

Publishes this plan on Comma: one URL that opens without state or cloud access, comments pinned to the exact resource, and an approve / request changes sign-off tied to this revision. The next plan becomes a new revision. Free, no card.

How the plan is read

  • Actions come from each resource_changes[].change.actions. ["delete","create"] and ["create","delete"] both mean replace (the order says whether the new object is created before the old one is destroyed). A no-op with importing is an import. A no-op with previous_address is a moved block. Unchanged resources are counted but not listed.
  • Reasons are Terraform’s action_reason in plain words: tainted, can’t update in place, -replace, replace_triggered_by, removed from configuration, count or for_each key gone.
  • Attributes are before and after flattened to paths like tags.Env or cidr_blocks[1]. Only changed paths are shown for updates. Paths in replace_paths are flagged as forcing replacement.
  • Masks: after_unknown becomes “(known after apply)”, and before_sensitive / after_sensitive become “(sensitive value)”. A sensitive value never reaches the report.
  • Drift is resource_drift: objects someone changed outside Terraform since the last apply.

Getting the plan JSON

WhereCommand
Terraformterraform plan -out=tfplan && terraform show -json tfplan > plan.json
OpenTofutofu plan -out=tfplan && tofu show -json tfplan > plan.json
Terragruntterragrunt show -json tfplan > plan.json (per unit)
Streamingterraform plan -json > plan.ndjson (actions only, no values)
HCP TerraformGET /api/v2/plans/:plan_id/json-output (needs admin on the workspace)
GitHub Actionsrun show -json in the same job after plan, then upload plan.json as an artifact

Plan review for people who don’t run Terraform

The plan text in a CI log works for the engineer who wrote the change. It doesn’t work for the security lead approving a new public bucket, the DBA checking a major version upgrade, or the change-advisory board that has to sign off before prod. They need what gets destroyed, in order, without scrolling 2,000 lines. Published to Comma, the plan is one link. They comment on the resource that worries them and approve it, and an agent can publish it from the pipeline on every plan. More in how to share a Terraform plan, sharing a Pulumi preview and, for the static analysis side, sharing a Checkov report.

Questions

How do I get a Terraform plan as JSON?
Save the plan to a file, then print it as JSON: terraform plan -out=tfplan, then terraform show -json tfplan > plan.json. OpenTofu is the same with tofu in place of terraform. Paste or drop plan.json here. The output of terraform plan -json (one JSON object per line) works too, but it only lists actions, not attribute values.
What does the viewer show?
Terraform's own summary line (Plan: N to add, N to change, N to destroy), a callout listing every resource that will be destroyed or replaced, then each change grouped by action: destroy, replace, update, create, import, move, forget and data reads. Each resource opens to an attribute table with before and after values, and the attribute that forces a replacement is flagged. Output changes and resources changed outside Terraform (drift) get their own sections.
Why is my instance being replaced?
Look for the attribute flagged “forces replacement”. Terraform records it in replace_paths when a provider can't change that attribute in place (an AMI, a subnet, a name on some resources). The reason next to the address says whether it was that, a taint, -replace, or replace_triggered_by, and whether the new object is created before the old one is destroyed.
Are sensitive values shown?
No. Anything Terraform marked sensitive in before_sensitive or after_sensitive (a password, a provider-flagged attribute, a sensitive variable) shows as “(sensitive value)”, and an unchanged sensitive attribute isn't listed at all. Values that won't exist until apply show as “(known after apply)”. The plan JSON itself does contain the raw values, so treat the file like a secret.
Can I post the plan on a pull request?
Yes. “Copy Markdown” gives the summary line and a table of every resource change with its action and reason — short enough for a PR comment, where the full plan text often runs past GitHub's 65,536-character limit. For the attribute-level diff, publish the report and put the link in the comment.
Is my plan uploaded?
No. The plan is parsed and the report built in your browser. It only leaves your machine if you click “Get a shareable link” and publish the report — and the published report contains only what you see in the preview, with sensitive values already masked.

Related: SARIF viewer · Trivy viewer · JSON diff · JSON viewer · what a Terraform plan is · all free tools