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.
5.4 Formatting and Style Adjustments
Quick Answer:
terraform fmtrewrites 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-recursiveto walk subdirectories. In CI,terraform fmt -check(often with-diff) exits non-zero when files are not formatted. Contrast that withterraform validate(syntax and consistency, no remote APIs) andterraform 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 fmton your modules. New formatting rules are not considered a breaking change. - If you truly reject the style, you may skip
fmtor 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
| Flag | Exam-useful behavior |
|---|---|
-recursive | Also process subdirectories. Default is only the specified or current directory |
-check | Do not treat the tree as already good: exit 0 if input is properly formatted, non-zero otherwise, and list improperly formatted file names |
-diff | Print the formatting diffs |
-write=false | Do not overwrite files. Implied by -check and by STDIN |
-list=false | Do not list files that needed changes |
-no-color | Plain 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/planproblems).
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.
| Command | Question it answers | Touches remote APIs / state? | Changes files or infra? |
|---|---|---|---|
terraform fmt | Is the HCL laid out in canonical style? | No | Rewrites files (unless -check / -write=false) |
terraform validate | Is 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 exist | Changes nothing |
terraform plan | If I applied, what would be created, updated, destroyed, or replaced? | Yes by default (refresh). Compares config + state + live objects | Does 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
fmtis notvalidate. Pretty code can still be invalid; valid code can still be ugly.fmtis notplan. It will not tell you a replace is coming.- Default
fmtis not recursive. Nested modules stay messy until you pass-recursive. -checkis the CI switch. Forgetting it means CI formats nothing and fails at nothing.fmtdoes not need cloud credentials and does not refresh state.- Aligning
=is style. Renaming resources, addingprevent_destroy, or pinning providers is notfmt.
Official references: terraform fmt command, Style Guide, and terraform validate command.
What does terraform fmt do on Terraform Associate (004)?
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?
How should you separate terraform fmt, terraform validate, and terraform plan on the 004 exam?