CLI tool
minamijoyo/hcledit avatar
minamijoyo/hcledit

hcledit edits HCL from the command line without losing comments

A command line editor for HCL

559 stars33 forksGoMIT

At a glance

What is it?
hcledit is a Go command line tool for reading HCL from standard input, editing it and writing it back to standard output, with token based operations on attributes and blocks that preserve the comments and layout of a file. It is schemaless, supports HCL2 only, and is MIT licensed.
Who is it for?
hcledit suits a team that wants to refactor HCL files in bulk from a script, since it reads stdin, writes stdout and preserves comments, which is what makes it usable in a migration pipeline or a lint fix loop rather than only by hand. It is the wrong tool for editing HCL1, because it supports HCL2 only, and it is the wrong tool if you want schema validation, because it is deliberately schemaless and will happily accept structures your application would reject.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 28 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why a command line editor for HCL

hcledit is described as a command line editor for HCL, and the README's first feature line explains the shape of it.

CLI-friendly: read HCL from stdin, edit and write to stdout, easily pipe and combine other commands.

That single decision is what makes the tool different from the alternatives. A Terraform configuration is a structured document with comments and formatting that a human maintains, and the common approach to changing one programmatically is to write a script that regenerates the file, which loses everything the generator does not know about. hcledit instead takes the existing text as input and emits edited text as output, so it fits into a pipeline the way a filter does.

The second feature line names the mechanism: token-based edit, so you can update lots of existing HCL files with automation scripts without losing comments.

The third is the constraint that shapes everything else: schemaless, with no dependency on a specific HCL application binary or schema. And then the version: support HCL2, not HCL1.

The README is candid about the design boundary. The hcledit focuses on editing HCL with command line and doesn't aim for generic query tools. It was originally born for refactoring Terraform configurations, but it's not limited to specific applications. And the HCL specification is somewhat generic, so usability takes precedence over strictness if there is a room for interpreting meanings in the schemaless approach.

That last sentence is the one to remember. This is a tool that makes a judgement call in favour of the user experience rather than the specification, which is the right call for a refactoring tool and the wrong call for a validator.

The primary language is Go, the licence is MIT, and the last push was on 2026-09-26 with the latest release v0.2.18 from 2026-05-05.

Installing it

There are three routes, and they cover the three situations people are in.

If you are a macOS user, Homebrew:

bash
brew install minamijoyo/hcledit/hcledit

Otherwise you can download the latest compiled binaries from the releases page and put it anywhere in your executable path.

And from source, if you have a Go 1.26+ development environment:

bash
git clone https://github.com/minamijoyo/hcledit
cd hcledit/
make install
hcledit version

The Makefile shows what install does, and it is worth noting that the target is minimal. There is a deps target that runs go mod download, and install depends on it and then runs go install. The build target differs only in that it writes to bin/$(NAME) rather than the Go bin directory, so a local build gives you a binary you can run from the tree.

The Go version requirement has a small inconsistency between the two places it appears. The README asks for Go 1.26+ to build from source, and the Dockerfile builds on golang:1.26-alpine3.23, but the go.mod declares go 1.25.0. So the module itself targets an older language version and the README and the container image are ahead of it.

The Dockerfile is more than a build recipe. It has three stages, and the middle one is unusual. The runtime image is alpine:3.23 with bash, git, openssh-client, tar, gzip and ca-certificates, and it copies the hcledit binary from the builder stage. But it also builds github.com/hub from source in a separate stage, because the linux binary for hub cannot run on alpine, with the upstream issue referenced in a comment. The entrypoint is hcledit itself, which tells you the image is meant to be run as a one-shot filter in a pipeline rather than as a long-lived service.

The command surface

The command list is short, and the README shows it in full.

bash
hcledit --help

There are five top-level commands: attribute, block, body, fmt, help and version. The attribute subcommand covers append, get, mv, replace, rm and set. The block subcommand covers append, get, list, mv, new and rm. The body subcommand has a single operation, get. And fmt formats a file to a canonical style.

The two global flags are the ones that determine how the tool is used in a script. There is -f, --file string, described as a path of input file with a default of "-", and there is -u, --update, which updates files in-place.

The default of "-" for the file flag is what makes the pipe behaviour work, and it is why the examples all pipe from cat. The -u flag is the alternative for editing files in place rather than printing to standard output, and the two together cover the two useful shapes: transform a stream, or rewrite a file.

The address syntax is a dotted path with the resource type first, then labels, then the attribute name. For the attribute examples the README uses resource.foo.bar.nested.attr2, which reads as the resource type foo, the label bar, a nested block, and the attribute attr2 inside it. The same shape is used for block operations, where resource.foo.bar identifies the block itself.

The attribute commands are deliberately distinct from one another. set changes the value of an existing attribute and fails if it is not there. append adds a new one, with a --newline flag controlling whether it goes on its own line. mv moves an attribute, renaming the key while keeping the value, and the README describes it as Move attribute, with the parenthetical Rename attribute key. replace does both at once, changing the name and the value. rm removes it. get prints the value, quoted as a string in the example because the value is an HCL string literal.

Editing a file and keeping the rest intact

The examples make the value proposition concrete, because every one of them shows the whole file after the operation and the untouched parts are still there.

Getting an attribute out is a pipe and a path:

bash
cat tmp/attr.hcl | hcledit attribute get resource.foo.bar.nested.attr2

Setting one changes the value and leaves the surrounding structure alone:

bash
cat tmp/attr.hcl | hcledit attribute set resource.foo.bar.nested.attr2 '"val3"'

Moving one changes the key and keeps the value:

bash
cat tmp/attr.hcl | hcledit attribute mv resource.foo.bar.nested.attr2 resource.foo.bar.nested.attr3

And replacing does both at once, so the output shows a renamed key with a new value:

bash
cat tmp/attr.hcl | hcledit attribute replace resource.foo.bar.nested.attr2 attr3 '"val3"'

Two details in these examples are worth pausing on. The value is passed as a quoted string including its own quotation marks, so '"val3"' in single quotes is what tells hcledit the value is a string rather than a bare word. And a block operation can reach into a nested block by path, which is the part that a text or regex based approach cannot do reliably.

The block operations work the same way, with a listing first:

bash
cat tmp/block.hcl | hcledit block list

which prints the dotted addresses of the blocks in the file, one per line, and then get, mv, rm, append and new operate on those addresses. The append example adds a new block inside an existing one with a --newline flag, and new creates one at the top level of the file.

The body get command is the odd one out and the most useful for inspection. It takes a block address and prints the body's contents without the block header itself:

bash
cat tmp/body.hcl | hcledit body get resource.foo.bar

The fmt command normalises formatting, and the example it works on is deliberately small: a file where one attribute has no spaces around its equals sign, which comes back with the spacing fixed and nothing else altered.

Address escaping, and the limits of the model

One edge case has its own section in the README, and it is the kind of detail that separates a tool that has been used on real files from one that has been used on test files.

Address escaping is supported for labels that contain a dot.

Because the address syntax is dot separated, a label containing a dot would otherwise be read as two path segments. The example uses a resource whose label itself contains a dot:

bash
cat tmp/attr.hcl | hcledit attribute get 'resource.foo\.bar.nested.attr2'

The backslash before the dot tells the address parser that the dot belongs to the label rather than separating segments.

That is the whole escaping story, and it is a narrow one, which tells you something about the scope. The tool understands labels containing dots and nothing else about labels with unusual characters, and it does not attempt to be a general query language over HCL. The README says as much directly, that hcledit doesn't aim for generic query tools.

The schemaless property is the other limit, and it is a deliberate one. Because there is no dependency on a specific application binary or schema, hcledit will happily parse and rewrite a file whose structure means nothing to Terraform, and it will not tell you that an attribute you set is one the application rejects. The upside is that the same tool works for Terraform, for Packer, for Nomad, for Consul and for any other HCL2 consumer, and for files that are not application configuration at all. The downside is that validation is somebody else's problem.

The other structural limit is the HCL2 requirement. Configurations written for HCL1, which was the original Terraform syntax, are out of scope entirely, and the README lists support HCL2, not HCL1 as a feature rather than a limitation.

Where hcledit fits, and where it stops

The comparison worth making is with sed and grep, and with the Terraform language server.

Against sed and grep, the difference is that hcledit understands the structure of the document. A regular expression can find the string attr2 = "val2" but it cannot tell whether it is inside the block you meant, whether it appears twice, or whether it appears inside a comment. A path-addressed edit on a parsed document gets those right by construction, and the fact that the untouched parts of the file come back byte for byte is what makes it safe to run over hundreds of files.

Against the Terraform language server and the terraform commands themselves, the difference is that hcledit is schemaless and does not need Terraform installed. You can use it in a container that has no Terraform, in a CI job that only needs to rewrite a configuration, or on HCL for a different application entirely. The trade is that the language server knows what a valid Terraform configuration looks like and can tell you that your edit is wrong.

The practical uses are the ones the README was born for: refactoring Terraform configurations. Renaming a resource type across a repository, changing a region in every provider block, moving a nested block, or removing an attribute that a policy says should not be there. All of those are bulk operations over many files, and all of them are the kind of change where a human-edited diff is unmanageable.

The limits are worth naming. It supports HCL2 only. It is schemaless, so it does not validate. It has one documented escaping rule, for dots in labels. And the address syntax is positional and hard coded, so a script that has to work across differently shaped configurations needs to discover the addresses first, which the block list command provides for blocks but not for attributes, where there is no equivalent listing command.

That last asymmetry is the one to be aware of. You can list blocks and work with them generically, but for attributes you generally need to know the path you want in advance, which means the script has to encode knowledge of the configuration's shape, and that is exactly the knowledge the schemaless design refuses to require.

On maintenance, the release history is v0.2.18 from 2026-05-05, v0.2.17 from 2025-02-12 and v0.2.16 from 2025-02-05, the last push was on 2026-09-26, and the repository is not archived. The MIT licence lets you use it in commercial tooling without restriction.

Editorial conclusion

hcledit suits a team that wants to refactor HCL files in bulk from a script, since it reads stdin, writes stdout and preserves comments, which is what makes it usable in a migration pipeline or a lint fix loop rather than only by hand. It is the wrong tool for editing HCL1, because it supports HCL2 only, and it is the wrong tool if you want schema validation, because it is deliberately schemaless and will happily accept structures your application would reject. Before adopting it, read the address escaping note, because a label containing a dot needs a backslash in the path expression, and check the fmt command against your own formatting expectations before using it across a repository.

Frequently asked questions

What is hcledit?

It is a command line editor for HCL, written in Go and MIT licensed. It reads HCL from standard input, edits it and writes it to standard output, so it can be piped and combined with other commands, and it supports HCL2 but not HCL1.

How do I install hcledit?

On macOS with brew install minamijoyo/hcledit/hcledit, or download the compiled binaries from the releases page and put them in your executable path, or build from source with git clone, make install and hcledit version if you have a Go 1.26+ environment. The README asks for Go 1.26+ while the go.mod declares go 1.25.0.

Which operations does hcledit support?

For attributes: append, get, mv, replace, rm and set. For blocks: append, get, list, mv, new and rm. For bodies: get. There is also a fmt command that formats a file to a canonical style, plus version and help.

Does hcledit lose comments when it edits a file?

No. The edit is token based, so you can update lots of existing HCL files with automation scripts without losing comments, and the README's examples show the whole file after each operation with the untouched parts preserved.

How do I address a label that contains a dot?

Address escaping is supported for labels that contain a dot. Escape it with a backslash in the path expression, for example hcledit attribute get 'resource.foo\.bar.nested.attr2', so the parser treats the dot as part of the label rather than as a path separator.

Does hcledit validate the configuration it edits?

No. It is schemaless, with no dependency on a specific HCL application binary or schema, and the README says usability takes precedence over strictness where there is room for interpreting meanings. That is what lets it work across Terraform and other HCL2 applications, but it means validation is left to you.

Official sources

  1. Issues
  2. License: MIT
  3. minamijoyo/hcledit on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/minamijoyo-hcledit.svg)](https://hysenlabs.com/projects/minamijoyo-hcledit)

Was this useful?