README
¶
tfsort
An opinionated sorter for Terraform configuration files.
Warning: This tool is currently under development and is not yet recommended for production use. Use at your own risk, and always review changes before applying them, especially when using the
--in-placeflag.
tfsort is a command-line tool written in Go that enforces a predictable order in your Terraform .tf files. It sorts:
- top-level blocks (e.g.
terraform,provider,variable,locals,data,resource,module,output) based on a predefined order. resourceanddatablocks first by type (e.g.,aws_iam_rolebeforeaws_s3_bucket) and then by name lexicographically.- elements within list attributes lexicographically, with handling of comments and mixed data types.
This keeps your configuration organised, reduces noisy diffs, and makes code reviews easier.
Features
- Sorts top-level blocks according to a standard convention (
terraform,provider,variable, etc.). - Sorts
resourceanddatablocks by type then by name. - Sorts elements within list attributes lexicographically with mixed-type handling.
- Comment preservation – comments stick with their associated list elements during sorting.
- Ignore sorting for specific list attributes using
// tfsort:ignoreor# tfsort:ignorecomments. - Zero external dependencies – a single static binary per platform.
Installation
Using pre-built binaries (recommended)
Binaries for Mac, Linux, and Windows are available on the releases page.
Using go install
go install github.com/tjun/tfsort/cmd/tfsort@latest
Go ≥ 1.24 is required.
Usage
tfsort [flags] <file1.tf> [file2.tf] [...]
If no files are given, tfsort reads from stdin and writes the sorted output to stdout.
Common flags
| Short | Long flag | Default | Description |
|---|---|---|---|
-r |
--recursive |
false | Walk directories recursively and process all *.tf files. |
-i |
--in-place |
false | Overwrite files in place. For file inputs, files are only overwritten if changes are made. If no changes are necessary, the file is not touched. If input is from stdin, a warning is logged and output is written to stdout. |
--no-sort-blocks |
false | Disable sorting of top-level blocks (default: enabled). | |
--no-sort-type-name |
false | Disable sorting of resource/data blocks by type and name (default: enabled). |
|
--no-sort-list |
false | Disable sorting of list attribute values (default: enabled). | |
--dry-run |
false | Exit with status code 1 if any files would be changed, 0 otherwise. No files are written. | |
-h |
--help |
Print help. | |
-v |
--version |
Print version. |
Detailed Sorting Rules
tfsort applies the following sorting logic to your Terraform files:
1. Top-Level Block Sorting
Top-level blocks (those not nested within other blocks) are sorted according to a predefined conventional order. This helps in organizing the overall structure of a .tf file. The standard order is:
terraformprovidervariablelocalsdatamoduleresourceoutput
Blocks of the same type maintain their relative order unless further sorting rules (like resource type/name sorting) apply. Unknown block types are typically sorted after all known types. This sorting can be disabled using the --no-sort-blocks flag (sorting is enabled by default).
2. Resource and Data Block Sorting
Within the resource and data block types, blocks are further sorted:
- Primary Sort: By Type: Blocks are first grouped and sorted alphabetically by their type label (e.g.,
aws_iam_rolecomes beforeaws_s3_bucket). - Secondary Sort: By Name: Within each type group, blocks are then sorted alphabetically by their name label (e.g., for
aws_s3_buckettype,alpha_bucketcomes beforezeta_bucket).
This ensures a consistent and predictable ordering for all your resource and data declarations. This sorting can be disabled using the --no-sort-type-name flag (sorting is enabled by default).
Example:
// Before:
resource "aws_s3_bucket" "config_storage" {}
resource "aws_iam_role" "app_role" {}
data "aws_caller_identity" "current" {}
resource "aws_s3_bucket" "asset_storage" {}
// After tfsort:
data "aws_caller_identity" "current" {} // Data blocks sorted with resources by type/name
resource "aws_iam_role" "app_role" {}
resource "aws_s3_bucket" "asset_storage" {}
resource "aws_s3_bucket" "config_storage" {}
3. List Attribute Sorting
Elements within list attributes are sorted lexicographically based on their HCL string representation.
- Simple Types: For lists containing simple types like strings or numbers, the sorting is straightforward.
- Numbers are compared based on their numerical value (e.g.,
22comes before80,443). - Strings are compared alphabetically (e.g.,
"alpha"comes before"beta").
- Numbers are compared based on their numerical value (e.g.,
- Mixed Types: When a list contains a mix of types (numbers, strings, objects/blocks):
- Numbers are generally sorted before strings.
- Strings are generally sorted before objects/blocks (which start with
{). - The exact order depends on the HCL string representation of each element as generated by the underlying HCL library, compared lexicographically (with numbers being a special case for numerical comparison).
- Ignoring List Sorting: To prevent a specific list from being sorted, place a
// tfsort:ignoreor# tfsort:ignorecomment immediately after the list's opening square bracket[, either on the same line or the next. (Refer to the "Ignoring List Sorting" section for examples).
Advanced List Features
tfsort handles various complex list scenarios:
Mixed Type Lists with Comments:
// Before:
security_group_rules = [
{ type = "ingress", from_port = 22, protocol = "tcp" },
"http-80-tcp", # HTTP traffic
1024, # Custom port
"ssh-22-tcp", # SSH access
]
// After tfsort:
security_group_rules = [
1024, # Custom port
"http-80-tcp", # HTTP traffic
"ssh-22-tcp", # SSH access
{ type = "ingress", from_port = 22, protocol = "tcp" },
]
Function Calls with Lists:
// Before:
allowed_ports = toset([
"https-443-tcp",
"http-80-tcp",
"ssh-22-tcp",
])
// After tfsort:
allowed_ports = toset([
"http-80-tcp",
"https-443-tcp",
"ssh-22-tcp",
])
(Note: Attributes that are not lists (e.g., maps, simple string/number attributes) are not reordered based on their keys by tfsort. Their formatting might be normalized by the HCL writing library, but their relative order within a block is preserved.)
Enhanced Comment Handling
tfsort provides comment preservation that ensures comments stay with their associated elements during sorting. This feature has been significantly improved to handle complex commenting scenarios:
- Leading Comments Stick to Elements: Comments that appear immediately before an element are treated as "leading comments" associated with that element. When the list is sorted, these comments move together with their associated element.
- Inline Comments Preserved: Comments that appear on the same line as list elements (e.g.,
"item", # comment) are perfectly preserved and move with their element. - Proper Multi-line Formatting: Both single-line and multi-line lists maintain proper formatting with appropriate trailing commas and newlines.
- Mixed Comment Styles: Both
//and#comment styles are fully supported throughout. - Visual Grouping Not Preserved: Blank lines or comments intended to visually separate groups of elements within a single list are not treated as sorting boundaries. The entire list's elements are sorted together based on the element content.
Example:
Consider this input list structured with comments and blank lines:
# Before tfsort:
example_list = [
# Group A
"charlie",
"alpha",
# Group B
"bravo",
]
After running tfsort, the elements will be sorted alphabetically, with comments moving with the element they precede, and the blank line likely removed:
# After tfsort:
example_list = [
# Group A
"alpha",
# Group B
"bravo",
# Group A
"charlie",
]
Notice that # Group B moved with "bravo", and the original grouping is lost due to the unified sort.
Workarounds for Preserving Groups/Order:
If you need to maintain specific groups of elements or a precise manual order within a list:
-
Use
# tfsort:ignore: Add the# tfsort:ignorecomment immediately after the opening bracket[to disable sorting for the entire list. -
Split the List: Define multiple separate lists and concat them. For example:
foods = concat( [ #fruits "apple", "banana", "cherry", ], [ #vegetables "broccoli", "carrot", "tomato", ] )In this case,
tfsortwould sort the elements withinlocal.group_aandlocal.group_bindividually, but theconcatfunction would preserve the group order incombined_list.
Ignoring List Sorting
To prevent a specific list attribute from being sorted, place a // tfsort:ignore or # tfsort:ignore comment immediately after the opening square bracket [ of the list. The comment can be on the same line as the bracket or on the immediately following line, before any list elements.
Examples of ignoring a list:
-
Ignore on the same line as the opening bracket:
resource "example" "demo" { mixed_list = [ # tfsort:ignore "omega", 100, "alpha", 20 ] another_attr = "sorted normally" // Note: only list sorting is affected by ignore } -
Ignore on the line after the opening bracket:
resource "example" "demo" { another_list = [ # tfsort:ignore "zulu", "yankee", "xray" ] } -
Using hash-style comments:
variable "custom_order_ports" { type = list(number) default = [ # tfsort:ignore 443, # HTTPS first - high priority 80, # HTTP second 22, # SSH last - restricted access ] } -
Complex lists with ignore directive:
locals { deployment_order = [ # tfsort:ignore - preserve deployment sequence { name = "database", priority = 1 }, { name = "backend", priority = 2 }, { name = "frontend", priority = 3 }, ] }
Lists that do not have this specific comment pattern will be sorted according to the standard lexicographic rules. The ignore directive only affects the specific list where it appears – other lists and block sorting continue normally.
Command Examples
Sort a single file and print the result to stdout:
tfsort main.tf
Sort all .tf files recursively in the current directory and its subdirectories, overwriting them in place if changes are needed:
tfsort -r -i ./
Check if any files in the modules/vpc directory would be changed by sorting, and exit with status 1 if so (otherwise 0):
tfsort --dry-run modules/vpc
Sort from stdin and write to stdout:
cat main.tf | tfsort
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success. Files were already sorted, or --in-place was used and changes were successfully applied. |
| 1 | Changes were detected or applied. This includes --dry-run detecting changes, or if the default behavior (writing to stdout) resulted in modified content. |
| 2 | Error during processing (e.g., parsing error, file I/O error). |
Contributing
Bug reports and pull requests are welcome! Please open an issue first if you plan a large change.
- Fork the repo and create your branch:
git checkout -b feature/xyz - Run
make testand ensure the linter passes (make lint) - Submit a PR describing your changes
License
tfsort is released under the MIT License.