What is the `moved` block, and how does it relate to `terraform state mv`?

5 minadvancedterraformmoved-blockrefactoringstate

Quick Answer

The `moved` block (Terraform 1.1+) declares, in configuration itself, that a resource address has changed — e.g., after renaming a resource or moving it into a module — so `terraform plan` automatically treats the old and new addresses as the same object instead of planning a destroy+create. Unlike `terraform state mv`, which is an imperative one-off CLI command you run locally and must remember to repeat for every teammate/environment, a `moved` block is committed to version control and applies automatically for anyone running `plan`/`apply` against that configuration, including in CI — making refactors safe and reproducible across the whole team.

Detailed Answer

Refactoring Terraform configuration — renaming a resource, splitting a monolithic root module into child modules, restructuring a for_each key — is one of the riskiest everyday operations, because Terraform judges "is this the same resource?" purely by address. Change the address and Terraform's default assumption is "the old one must be destroyed and a new one created."

The old way: terraform state mv

terraform state mv aws_instance.web aws_instance.web_server

This works, but it's an imperative, one-off command:

  • It must be run by hand, once, against a specific state file.
  • It isn't recorded anywhere — a teammate pulling the latest code has no way to know a rename happened, or that they need to run the same command against their own environment's state before applying.
  • In a multi-environment setup (dev/staging/prod each with separate state), you'd have to remember to run the same state mv command against every single environment's state, in order, without missing one.

The modern way: the moved block

moved {
  from = aws_instance.web
  to   = aws_instance.web_server
}

This is ordinary HCL, committed to version control alongside the rename itself. When anyone runs terraform plan against a configuration containing this block, Terraform automatically treats aws_instance.web in the prior state as being aws_instance.web_server now — no destroy/recreate, and no separate manual command required.

Why this is a meaningful improvement

  • It's declarative and reproducible. Every environment, every teammate, every CI run gets the exact same address translation automatically, just by having the updated configuration.
  • It's self-documenting. The moved block itself is a permanent record in the codebase of "this used to be called X" — useful context for anyone reading history later.
  • It composes with modules. moved blocks also handle the common case of moving a resource into (or out of) a module (from = aws_instance.web, to = module.compute.aws_instance.web), which is exactly the kind of refactor that would otherwise force a risky, manually-repeated state mv per environment.

moved blocks don't replace state mv entirely — for state-only edge cases with no configuration change to attach a block to, the CLI command is still useful — but for the common "I renamed/relocated a resource in code" case, moved is the safer, version-controlled default.

Related Resources