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: 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). Ano-opwithimportingis an import. Ano-opwithprevious_addressis amovedblock. Unchanged resources are counted but not listed. - Reasons are Terraform’s
action_reasonin plain words: tainted, can’t update in place,-replace,replace_triggered_by, removed from configuration, count or for_each key gone. - Attributes are
beforeandafterflattened to paths liketags.Envorcidr_blocks[1]. Only changed paths are shown for updates. Paths inreplace_pathsare flagged as forcing replacement. - Masks:
after_unknownbecomes “(known after apply)”, andbefore_sensitive/after_sensitivebecome “(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
| Where | Command |
|---|---|
| Terraform | terraform plan -out=tfplan && terraform show -json tfplan > plan.json |
| OpenTofu | tofu plan -out=tfplan && tofu show -json tfplan > plan.json |
| Terragrunt | terragrunt show -json tfplan > plan.json (per unit) |
| Streaming | terraform plan -json > plan.ndjson (actions only, no values) |
| HCP Terraform | GET /api/v2/plans/:plan_id/json-output (needs admin on the workspace) |
| GitHub Actions | run 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