python-hcl2 parses HCL2 into Python dicts and converts them back. This guide covers installation, everyday usage, and the CLI tools.
python-hcl2 requires Python 3.8 or higher.
pip install python-hcl2| Function | Description |
|---|---|
hcl2.load(file) |
Parse an HCL2 file to a Python dict |
hcl2.loads(text) |
Parse an HCL2 string to a Python dict |
hcl2.dump(data, file) |
Write a Python dict as HCL2 to a file |
hcl2.dumps(data) |
Convert a Python dict to an HCL2 string |
hcl2.parse(file) |
Parse an HCL2 file to a LarkElement tree |
hcl2.parses(text) |
Parse an HCL2 string to a LarkElement tree |
hcl2.parse_to_tree(file) |
Parse an HCL2 file to a raw Lark tree |
hcl2.parses_to_tree(text) |
Parse an HCL2 string to a raw Lark tree |
hcl2.transform(lark_tree) |
Transform a raw Lark tree into a LarkElement tree |
hcl2.serialize(tree) |
Serialize a LarkElement tree to a Python dict |
hcl2.from_dict(data) |
Convert a Python dict into a LarkElement tree |
hcl2.from_json(text) |
Convert a JSON string into a LarkElement tree |
hcl2.reconstruct(tree) |
Convert a LarkElement tree (or Lark tree) to HCL2 text |
hcl2.Builder() |
Build HCL documents programmatically |
hcl2.query(source) |
Query HCL documents with typed view facades |
For intermediate pipeline stages (parse_to_tree, transform, serialize, from_dict, from_json, reconstruct) and the Builder class, see Advanced API Reference.
Use load / loads to parse HCL2 into a Python dictionary:
import hcl2
with open("main.tf") as f:
data = hcl2.load(f)
# or from a string
data = hcl2.loads('resource "aws_instance" "web" { ami = "abc-123" }')The default serialization options are tuned for content fidelity — the output preserves enough detail (__is_block__ markers, heredoc delimiters, quoted strings like '"hello"', scientific notation, etc.) that it can be deserialized back into a LarkElement tree and reconstructed into valid HCL2 without information loss. This makes the defaults ideal for round-trip workflows (load → modify → dump), but it does add noise to the output compared to what you might expect from a plain JSON conversion. If you only need to read values and don't plan to reconstruct HCL2 from the dict, you can disable options like explicit_blocks and preserve_heredocs, or enable strip_string_quotes for cleaner output.
Pass serialization_options to control how the dict is produced:
from hcl2 import loads, SerializationOptions
data = loads(text, serialization_options=SerializationOptions(
with_meta=True,
wrap_objects=True,
))| Field | Type | Default | Description |
|---|---|---|---|
with_comments |
bool |
True |
Include comments as __comments__ and __inline_comments__ keys (see Comment Format) |
with_meta |
bool |
False |
Add __start_line__ / __end_line__ metadata |
wrap_objects |
bool |
False |
Wrap object values as inline HCL2 strings |
wrap_tuples |
bool |
False |
Wrap tuple values as inline HCL2 strings |
explicit_blocks |
bool |
True |
Add __is_block__: True markers to blocks. Mandatory for JSON->HCL2 deserialization and reconstruction. |
preserve_heredocs |
bool |
True |
Keep heredocs in their original form |
force_operation_parentheses |
bool |
False |
Force parentheses around all operations |
preserve_scientific_notation |
bool |
True |
Keep scientific notation as-is |
strip_string_quotes |
bool |
False |
Remove surrounding quotes from string values (e.g. "hello" instead of '"hello"'). Breaks JSON->HCL2 deserialization and reconstruction. |
When with_comments is enabled (the default), comments are included as lists of objects under the __comments__ and __inline_comments__ keys. Each object has a "value" key containing the comment text (with delimiters stripped):
from hcl2 import loads, SerializationOptions
data = loads(
"# Configure the provider\nx = 1\n",
serialization_options=SerializationOptions(with_comments=True),
)
data["__comments__"]
# [{"value": "Configure the provider"}]__comments__ contains standalone comments (on their own lines), while __inline_comments__ contains comments found inside expressions.
Note: Comments are currently read-only — they are captured during parsing but not restored when converting a dict back to HCL2 with
dump/dumps.
Use dump / dumps to convert a Python dictionary back into HCL2 text:
import hcl2
hcl_string = hcl2.dumps(data)
with open("output.tf", "w") as f:
hcl2.dump(data, f)Control how the dict is interpreted when building the LarkElement tree:
from hcl2 import dumps, DeserializerOptions
text = dumps(data, deserializer_options=DeserializerOptions(
object_elements_colon=True,
))| Field | Type | Default | Description |
|---|---|---|---|
heredocs_to_strings |
bool |
False |
Convert heredocs to plain strings |
strings_to_heredocs |
bool |
False |
Convert strings with \n to heredocs |
object_elements_colon |
bool |
False |
Use : instead of = in object elements |
object_elements_trailing_comma |
bool |
True |
Add trailing commas in object elements |
Control whitespace and alignment in the generated HCL2:
from hcl2 import dumps, FormatterOptions
text = dumps(data, formatter_options=FormatterOptions(
indent_length=4,
vertically_align_attributes=False,
))| Field | Type | Default | Description |
|---|---|---|---|
indent_length |
int |
2 |
Number of spaces per indentation level |
open_empty_blocks |
bool |
True |
Expand empty blocks across multiple lines |
open_empty_objects |
bool |
True |
Expand empty objects across multiple lines |
open_empty_tuples |
bool |
False |
Expand empty tuples across multiple lines |
vertically_align_attributes |
bool |
True |
Vertically align = signs in attribute groups |
vertically_align_object_elements |
bool |
True |
Vertically align = signs in object elements |
python-hcl2 ships three console scripts: hcl2tojson, jsontohcl2, and hq.
Convert HCL2 files to JSON.
hcl2tojson main.tf # print JSON to stdout
hcl2tojson main.tf output.json # write to file
hcl2tojson terraform/ output/ # convert a directory
cat main.tf | hcl2tojson - # read from stdinFlags:
| Flag | Description |
|---|---|
-s |
Skip un-parsable files |
--json-indent N |
JSON indentation width (default: 2) |
--with-meta |
Add __start_line__ / __end_line__ metadata |
--with-comments |
Include comments as __comments__ / __inline_comments__ object lists |
--wrap-objects |
Wrap object values as inline HCL2 |
--wrap-tuples |
Wrap tuple values as inline HCL2 |
--no-explicit-blocks |
Disable __is_block__ markers |
--no-preserve-heredocs |
Convert heredocs to plain strings |
--force-parens |
Force parentheses around all operations |
--no-preserve-scientific |
Convert scientific notation to standard floats |
--version |
Show version and exit |
Convert JSON files to HCL2.
jsontohcl2 output.json # print HCL2 to stdout
jsontohcl2 output.json main.tf # write to file
jsontohcl2 output/ terraform/ # convert a directory
cat output.json | jsontohcl2 - # read from stdinFlags:
| Flag | Description |
|---|---|
-s |
Skip un-parsable files |
--indent N |
Indentation width (default: 2) |
--colon-separator |
Use : instead of = in object elements |
--no-trailing-comma |
Omit trailing commas in object elements |
--heredocs-to-strings |
Convert heredocs to plain strings |
--strings-to-heredocs |
Convert strings with escaped newlines to heredocs |
--no-open-empty-blocks |
Collapse empty blocks to a single line |
--no-open-empty-objects |
Collapse empty objects to a single line |
--open-empty-tuples |
Expand empty tuples across multiple lines |
--no-align |
Disable vertical alignment of attributes and object elements |
--version |
Show version and exit |
Query HCL2 files by structure, with optional Python expressions.
hq 'resource.aws_instance.main.ami' main.tf
hq 'variable[*]' variables.tf --jsonFor the full guide, see hq Reference.
- Querying HCL (Python) — navigate documents with typed view facades
- Advanced API Reference — intermediate pipeline stages, Builder, pipeline diagram
- hq Reference — query HCL files from the command line
- hq Examples — validated real-world queries by use case