Cross-state plans

One plan. Every state it touches.

Change a producer module and watch the change ripple into every consumer — in the same plan. No apply-first ordering. No stale outputs. One plan, one apply.

One plan, one apply Reads pending outputs Same terraform_remote_state HCL
Get Started See the plan

The apply-first cycle is a tax you pay every day

Vanilla terraform_remote_state only ever reads the last applied state. A change in a producer module is invisible to its consumers until you apply the producer first — so you're stuck ordering plans and applies by hand.

// Terraform · remote_state
1edit producer (rm1)
2plan rm1
3apply rm1 ← required first
4plan rm2 (now sees new output)
5apply rm2
→ 2 plans, 2 applies, strict ordering
// Stategraph · cross-state
1edit producer (rm1)
2plan — shows rm1 and rm2
3apply — both states, one txn
  
  
→ 1 plan, 1 apply, no ordering
→ It gets worse: add a new output attribute and the consumer's plan fails outright — the attribute doesn't exist in applied state yet.

One plan spans two states

rm1 outputs a port. rm2 reads it and derives firewall rules. We change only rm1 — one plan recomputes both.

stategraph — cross-state plan
# edit rm1 only: http_proxy_port 8080 → 9090, then one plan
 stategraph plan

State 0 · rm1  (producer)
  ~ terraform_data.http_proxy_port
      input:  8080  9090
  ~ output.http_proxy_port:  8080  9090

State 1 · rm2  (consumer: reads rm1's pending output)
  ~ terraform_data.firewall_rule_port_min
      input:  8070  9080   # http_proxy_port - 10

Plan: 0 to add, 2 to change, 0 to destroy   # across 2 states
✓ one plan, one apply. rm2 never waited on apply-rm1-first

rm1: 8080 → 9090  ·  rm2 firewall ports recompute off the pending value  ·  in the same plan

It's the HCL you already write

No new syntax, no special resources. A normal terraform_remote_state data source is all it takes to adopt cross-state plans — see what's supported →

rm1/main.tf producer
resource "terraform_data" "http_proxy_port" {
  input = 8080
}

output "http_proxy_port" {
  value = terraform_data.http_proxy_port.output
}
rm2/main.tf consumer
data "terraform_remote_state" "project_a" {
  backend = "http"
  config = {
    address = "https://stategraph/<uuid>"
  }
}

resource "terraform_data" "firewall_rule_port_min" {
  # reads rm1's *pending* output, not last-applied
  input = data.terraform_remote_state.project_a.outputs.http_proxy_port - 10
}

What you get

Whole blast radius in one plan

Change one module and see every state the change touches — with real diffs — in a single plan. No guessing what's downstream.

Consumers read pending outputs

rm2 sees rm1's planned value, so it's correct before anything is applied. No more "last applied" lag.

One plan, one apply

No apply-A-then-plan-B ordering cycle. The whole change set commits as a single transaction.

New outputs just work

Add a new output attribute and consumers pick it up in the same plan. It's one transaction, not two disjoint states.

Cheaper reads

Stategraph doesn't pull the whole state file to read a couple of outputs — it reads just what you reference.

Zero adoption cost

The same terraform_remote_state HCL you already have is all you need. Point it at a Stategraph state and go.

Stop applying in order

Bring your existing terraform_remote_state wiring. Get cross-state plans, transactional applies, and the entire blast radius in one shot.

Get Started Read the Docs