Format and tidy up your HCL code in seconds for consistent indentation and easier-to-read configuration files.
.tf, .tfvars, .hcl, or .pkr.hcl file. The formatter accepts any HCL2 source — Terraform modules, Packer templates, Nomad job specs, Vault and Consul config snippets — and reports the line and column of any parse error inline.terraform fmt — 2 spaces per level, with = signs aligned inside each block of consecutive single-line arguments. Switch to 4 spaces or a tab character if your repo's .editorconfig overrides the canonical convention; toggle =-alignment off if you prefer one-argument-per-line diffs.<<EOT / <<-EOT heredocs) and string interpolations (${...}) are preserved verbatim; only structural whitespace changes..tf / .hcl file. Everything runs locally in your browser — no upload, no signup, no server log of your config — which matters when the file contains provider credentials, S3 bucket names, or IAM ARNs you'd rather not paste into a stranger's backend.HCL (HashiCorp Configuration Language) is the config DSL behind Terraform, Packer, Nomad, Vault, Consul, Boundary, and Waypoint. Unlike JSON or YAML, HCL was designed for humans first — it has line comments (# and //), block comments (/* ... */), heredoc strings, expressions, and functions — but that flexibility means hand-written or AI-generated snippets often drift from the canonical style that terraform fmt enforces. Auto-formatting matters because:
terraform fmt is part of most CI pipelines (the -check flag returns a non-zero exit code if any file would be reformatted). Reviewers don't want to see indent-only churn mixed with logic changes; formatting before commit keeps diffs focused on the actual resource changes.locals definitions line up identically across the repo.= signs). One pass through the formatter produces output you can compare side-by-side with hand-written reference modules.for_each patterns) is visible at a glance.| Property | HCL | JSON | YAML |
|---|---|---|---|
| Native comments | #, //, /* */ — first-class |
None in spec; Terraform's JSON syntax supports a "//" property convention |
# line comments |
| Multi-line strings | Heredocs (<<EOT and <<-EOT with leading-whitespace strip) |
Escape \n only |
Block scalars (` |
| Expressions / interpolation | ${var.x}, for expressions, function calls native |
None — values are literal | None in spec; some templating layers add it |
| Trailing commas | Allowed inside [ ] and { } |
Not allowed by RFC 8259 | Not applicable |
| Used by | Terraform, Packer, Nomad, Vault, Consul, Boundary, Waypoint | Terraform (.tf.json), AWS CloudFormation, Azure ARM, GCP Deployment Manager |
Kubernetes manifests, GitHub Actions, Ansible, Pulumi YAML, AWS SAM, CloudFormation (alt syntax) |
| Indent-significant | No — braces define structure | No — braces / brackets | Yes — indentation IS the structure |
| Canonical formatter | terraform fmt (built into Terraform CLI) |
Prettier, jq |
prettier, yamllint --fix, yq |
JSON is a valid Terraform input form via .tf.json files — but HashiCorp's docs explicitly recommend against hand-editing them, positioning JSON as the output of code generators (CDK for Terraform, terraform-cdk, custom Python scripts). YAML is not a native HCL alternative for HashiCorp tools; if you see YAML for infra-as-code it's almost always a different ecosystem (Kubernetes, Pulumi, CloudFormation alt syntax).
terraform fmt Canonical Style Rules| Rule | Canonical value | Notes |
|---|---|---|
| Indent | 2 spaces per level | Not customizable in terraform fmt itself; HashiCorp's docs call this "intentionally opinionated" |
= alignment |
Aligned within a contiguous block of single-line arguments | Breaks across blank lines, nested blocks, and multi-line values |
| Block separation | One blank line between top-level blocks | Extras are collapsed; missing blanks are inserted |
| Trailing whitespace | Stripped | End-of-line spaces and tabs removed |
| Quote style | Double quotes for strings | Single quotes are invalid HCL2 |
| Heredoc indent | <<-EOT strips leading whitespace to the closing marker's column |
<<EOT keeps whitespace verbatim |
| Trailing commas | Allowed inside [ ] / { } collection literals |
Not added or removed by fmt |
This web formatter targets the same canonical output as terraform fmt. Differences from the CLI: this tool runs on a single pasted snippet (no -recursive / -write / -check flags), exposes an indent-size toggle for shops that override the default, and leaves your file on your machine.
HCL has three features JSON lacks that matter for human-authored infra config: comments (line, block, and trailing), expressions and interpolation (${var.region}, for_each, dynamic blocks), and heredoc strings for embedding user-data scripts or policy documents without escaping every newline. JSON is a data-interchange format — it was designed to be parsed by machines, not edited by humans — so it has no comment syntax in the spec and no way to express count = length(var.subnets) without resorting to a string that Terraform interprets. Terraform's JSON syntax is the HashiCorp-recommended form only for programmatic generation (CDK for Terraform, custom code generators); for anything you'll read or review by hand, HCL wins decisively.
No. HCL2 is a distinct language with its own grammar — blocks are delimited by braces, arguments use =, expressions include function calls (length(), lookup(), merge()), and the type system has primitives, lists, sets, maps, objects, and tuples that don't all map cleanly to JSON. JSON is one interchange representation HCL supports — .tf.json files can express the same configuration but with awkward escapes for any expression. The relationship is closer to "HCL has a JSON serialization" than "HCL is a JSON superset." For comparison: see JSON Formatter if you're working with the .tf.json form, or JSON to YAML and YAML to JSON for moving between adjacent config formats.
terraform fmt or this web formatter?terraform fmt is canonical — it ships with the Terraform CLI, runs against your whole repo recursively (terraform fmt -recursive), and integrates with pre-commit hooks and CI (-check returns a non-zero exit if any file would change). Use it for any file checked into source control. This web tool is for the cases where the CLI isn't immediately available: reviewing a snippet from a colleague's Slack message, cleaning up an answer pasted from Discuss HashiCorp, formatting an AI-generated module before reading it, or working on a corporate laptop where you can't install Go binaries. Both target the same canonical output for basic indent + alignment.
HCL2 shipped with Terraform 0.12 on May 22, 2019 and is the version every modern HashiCorp tool uses. The key changes vs HCL1: first-class expressions (var.subnets[0].cidr instead of string interpolation everywhere), a real type system with collection types, for expressions and dynamic blocks, conditional expressions (condition ? true_val : false_val), and a unified parser shared (with some forking) across Terraform, Packer, Nomad, and others. HCL1 files in pre-0.12 Terraform required terraform 0.12upgrade to migrate. If you're writing new infra in 2026, you're on HCL2 — HCL1 is effectively dead outside legacy modules.
They all build on the hashicorp/hcl library, but the implementations have diverged. Terraform uses HCL2 directly. Packer uses HCL2 with the hcldec library and code-generated decoders. Nomad has historically pinned a slightly-forked HCL2 branch (hcl2-nomad-tweaks) to apply patches not yet upstreamed. Vault and Consul v1 use gohcl struct-tag decoding. Syntactically — block braces, =-assignment, heredocs, comments — the dialects are interchangeable; semantically each tool defines its own valid block names, attributes, and validation rules. This formatter handles the syntax layer (whitespace, indent, alignment), which is identical across all HashiCorp HCL2 tools.
It does a parse-and-re-emit, so any HCL2 syntax error (unclosed brace, invalid attribute name, malformed heredoc, unterminated string) is caught and reported with a line and column. What it does NOT do is semantic validation — it won't tell you that aws_instance doesn't accept an instnace_type (typo) attribute, or that a count and for_each can't appear on the same resource. For semantic checks against a real provider schema, run terraform validate (after terraform init); for opinionated linting (security, naming, deprecated patterns), use tflint or checkov.
${...} interpolations — will the formatter break them?No. String interpolations ("${var.environment}-bucket") and expression interpolations inside heredocs are preserved exactly. The formatter operates on the AST and re-emits source text; it does not evaluate expressions or substitute variables. The only changes are whitespace and structural (indent, blank lines, = alignment). If your input parses, your output is byte-equivalent for every string literal, heredoc, and expression — just re-laid-out.
.tfvars and .pkr.hcl files?Both are HCL2 and parse the same way. .tfvars files contain attribute-value assignments only (no resource / module / provider blocks) — key = value per line. .pkr.hcl is Packer's HCL2 template format with source, build, variable, and locals blocks. The formatter accepts either; the canonical 2-space indent and = alignment rules apply uniformly. Packer also accepts JSON templates (.json) — those are a separate, legacy format being phased out in favor of HCL2.
No hard cap — everything runs in your browser tab. Performance is good on Terraform root modules up to a few thousand lines (~200 KB); on larger files (a generated module from CDK-for-Terraform with hundreds of resources) the parse-and-re-emit may pause the tab for a second or two. For monorepo-wide formatting, use terraform fmt -recursive from the CLI — it's faster and avoids the round-trip through your clipboard. To diff before/after, paste both versions into Text Diff.