5.4 Formatting and Style Adjustments

Key Takeaways

  • `terraform fmt` rewrites HCL to Terraform's canonical style. It is opinionated and has no style knobs.
  • Default scope is the current directory (or a path/file you pass). `-recursive` also formats subdirectories, which is how you catch nested modules.
  • `-check` exits non-zero when files need formatting; `-diff` prints the formatting delta. Those two flags are the CI pair.
  • `fmt` does **not** validate logic, evaluate variables, refresh state, or call provider APIs.
  • Contrast: `fmt` is style, `validate` is syntax and internal consistency without remote APIs, `plan` is the desired-versus-actual delta and may call APIs and refresh state.
Last updated: August 2026

5.4 Formatting and Style Adjustments

Quick Answer: terraform fmt rewrites Terraform configuration to the canonical format and style. It does not validate logic and it does not call APIs. Default is the current directory; add -recursive to walk subdirectories. In CI, terraform fmt -check (often with -diff) exits non-zero when files are not formatted. Contrast that with terraform validate (syntax and consistency, no remote APIs) and terraform plan (desired versus actual, may refresh state).

Objective 3g on Terraform Associate (004) is apply formatting and style adjustments to a configuration. The exam product is Terraform 1.12. Community Edition, HCP Terraform, and Terraform Enterprise all consume the same language; fmt is a local CLI concern that keeps that language consistent before a run ever starts.

What fmt is for

Official CLI docs: terraform fmt formats configuration file contents so they match the canonical format and style. It applies a subset of the Terraform language style conventions, plus minor readability adjustments.

HashiCorp is explicit about the design:

  • Commands that generate configuration already emit fmt-compliant HCL. Match that style in files you write by hand so generated and handwritten code agree.
  • The command is intentionally opinionated and has no customization options. The primary goal is consistency across codebases, not making everyone love every spacing choice.
  • Canonical format can change in minor ways between Terraform versions. After you upgrade, HashiCorp recommends running terraform fmt on your modules. New formatting rules are not considered a breaking change.
  • If you truly reject the style, you may skip fmt or use a third-party formatter—but then you should run that tool on Terraform-generated files too.

HashiCorp's style guide, which fmt only partially automates, includes: indent two spaces per nesting level; align equals signs for consecutive single-line arguments; put arguments above nested blocks; put meta-arguments such as count first and lifecycle last; separate top-level blocks with a blank line. fmt will rewrite a large subset of those rules. It will not rename aws_instance.webAPI to web_api or add a description to a variable. Those are human style-guide items, not formatter jobs.

Usage and the flags 004 tests

terraform fmt [options] [target...]

With no target, fmt scans the current directory. You can pass:

  • a directory
  • a specific file
  • - for standard input
FlagExam-useful behavior
-recursiveAlso process subdirectories. Default is only the specified or current directory
-checkDo not treat the tree as already good: exit 0 if input is properly formatted, non-zero otherwise, and list improperly formatted file names
-diffPrint the formatting diffs
-write=falseDo not overwrite files. Implied by -check and by STDIN
-list=falseDo not list files that needed changes
-no-colorPlain output

The CI pair is terraform fmt -check -recursive or terraform fmt -check -diff -recursive. A pre-commit hook usually runs terraform fmt -recursive so the commit itself is already canonical. HashiCorp's style guide says to run terraform fmt and terraform validate before you commit.

fmt does not require a successful terraform init to rewrite files. It is looking at HCL text. That is different from validate, which official docs say requires an initialized working directory so referenced plugins and modules are installed.

What fmt does not do

This is the heart of 3g.

  • It does not parse whether instance_type = "t99.gigantic" is a real AWS type.
  • It does not evaluate variables, locals, or count.
  • It does not open a backend or read state.
  • It does not call provider APIs.
  • It does not refresh or lock state.
  • It does not apply a saved plan.
  • It does not prove the configuration is internally consistent (wrong resource argument names are validate / plan problems).

If a file is not valid enough to parse, fmt cannot canonicalize it and you will see a syntax error. That is still not "fmt validated my module."

Contrast: fmt versus validate versus plan

004 loves to mix the three write-path commands. Keep them in separate boxes.

CommandQuestion it answersTouches remote APIs / state?Changes files or infra?
terraform fmtIs the HCL laid out in canonical style?NoRewrites files (unless -check / -write=false)
terraform validateIs the configuration syntactically valid and internally consistent (argument names, types), regardless of variables or existing state?No remote state or provider APIs. Needs init so plugins/modules existChanges nothing
terraform planIf I applied, what would be created, updated, destroyed, or replaced?Yes by default (refresh). Compares config + state + live objectsDoes not change infra; may write refreshed state

Official validate docs: it does not validate remote services such as remote state or provider APIs. Official plan docs: plan reads remote objects so state is up-to-date, then proposes actions. Official fmt docs never mention backends, providers, or plans.

A configuration can be perfectly formatted and still be nonsense (ami = "not-an-ami" aligned on column 16). fmt is happy. validate may be happy if the type is string. plan is the first command that asks AWS whether that AMI exists in this account. Conversely, a valid, planned configuration can still fail CI if someone used tabs and -check is on.

terraform apply is not a formatter. Do not run apply to "fix style."

Worked scenario

A module author lands a pull request that adds modules/network/main.tf. Locally they ran terraform fmt in the repo root without -recursive, so the nested module still has ragged equals signs. CI runs terraform fmt -check -recursive and fails. terraform validate in the same pipeline is green because the configuration is consistent. No cloud credential was used for the fmt step.

They rerun terraform fmt -recursive, commit the rewrite, and CI goes green. A reviewer then looks at terraform plan (or an HCP Terraform speculative plan on the pull request) to judge the infrastructure delta. Style was a separate gate from the execution plan.

After the team upgrades from Terraform 1.7 to 1.12, they run terraform fmt -recursive again because HashiCorp documents that canonical format may shift slightly and that new fmt rules are not breaking changes. They do not treat that diff as an infrastructure change.

Exam traps for objective 3g

  • fmt is not validate. Pretty code can still be invalid; valid code can still be ugly.
  • fmt is not plan. It will not tell you a replace is coming.
  • Default fmt is not recursive. Nested modules stay messy until you pass -recursive.
  • -check is the CI switch. Forgetting it means CI formats nothing and fails at nothing.
  • fmt does not need cloud credentials and does not refresh state.
  • Aligning = is style. Renaming resources, adding prevent_destroy, or pinning providers is not fmt.

Official references: terraform fmt command, Style Guide, and terraform validate command.

Test Your Knowledge

What does terraform fmt do on Terraform Associate (004)?

A
B
C
D
Test Your Knowledge

A CI job must fail when any .tf file in the repository, including nested modules, is not in canonical style. Which invocation matches official fmt flags?

A
B
C
D
Test Your Knowledge

How should you separate terraform fmt, terraform validate, and terraform plan on the 004 exam?

A
B
C
D