Getting Started

April 7, 2026 · View on GitHub

python-hcl2 parses HCL2 into Python dicts and converts them back. This guide covers installation, everyday usage, and the CLI tools.

Installation

python-hcl2 requires Python 3.8 or higher.

pip install python-hcl2

For the CLI tools only (hcl2tojson, jsontohcl2, hq), pipx installs them globally in an isolated environment:

pipx install python-hcl2

Quick Reference

FunctionDescription
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.

HCL to Python dict

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" }')

SerializationOptions

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,
))
FieldTypeDefaultDescription
with_commentsboolTrueInclude comments as __comments__ and __inline_comments__ keys (see Comment Format)
with_metaboolFalseAdd __start_line__ / __end_line__ metadata
wrap_objectsboolFalseWrap object values as inline HCL2 strings
wrap_tuplesboolFalseWrap tuple values as inline HCL2 strings
explicit_blocksboolTrueAdd __is_block__: True markers to blocks. Mandatory for JSON->HCL2 deserialization and reconstruction.
preserve_heredocsboolTrueKeep heredocs in their original form
force_operation_parenthesesboolFalseForce parentheses around all operations
preserve_scientific_notationboolTrueKeep scientific notation as-is
strip_string_quotesboolFalseRemove surrounding quotes from string values (e.g. "hello" instead of '"hello"'). Breaks JSON->HCL2 deserialization and reconstruction.

Comment Format

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.

Python dict to HCL

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)

DeserializerOptions

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,
))
FieldTypeDefaultDescription
heredocs_to_stringsboolFalseConvert heredocs to plain strings
strings_to_heredocsboolFalseConvert strings with \n to heredocs
object_elements_colonboolFalseUse : instead of = in object elements
object_elements_trailing_commaboolTrueAdd trailing commas in object elements

FormatterOptions

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,
))
FieldTypeDefaultDescription
indent_lengthint2Number of spaces per indentation level
open_empty_blocksboolTrueExpand empty blocks across multiple lines
open_empty_objectsboolTrueExpand empty objects across multiple lines
open_empty_tuplesboolFalseExpand empty tuples across multiple lines
vertically_align_attributesboolTrueVertically align = signs in attribute groups
vertically_align_object_elementsboolTrueVertically align = signs in object elements

CLI Tools

python-hcl2 ships three console scripts: hcl2tojson, jsontohcl2, and hq.

hcl2tojson

Convert HCL2 files to JSON. Accepts files, directories, glob patterns, or stdin (default when no args given).

hcl2tojson main.tf                          # single file to stdout
hcl2tojson main.tf -o output.json           # single file to output file
hcl2tojson terraform/ -o output/            # directory to output dir
hcl2tojson --ndjson terraform/               # directory to stdout (NDJSON)
hcl2tojson --ndjson 'modules/**/*.tf'       # glob + NDJSON streaming
hcl2tojson a.tf b.tf -o output/             # multiple files to output dir
hcl2tojson --only resource,module main.tf   # block type filtering
hcl2tojson --fields cpu,memory main.tf      # field projection
hcl2tojson --compact main.tf                # single-line JSON
echo 'x = 1' | hcl2tojson                  # stdin (no args needed)

Exit codes: 0 = success, 1 = partial (some skipped), 2 = all unparsable, 4 = I/O error.

Flags:

FlagDescription
-o, --outputOutput path (file for single input, directory for multiple)
-sSkip un-parsable files
-q, --quietSuppress progress output on stderr
--ndjsonOne JSON object per line (newline-delimited JSON). Multi-file adds __file__ provenance key.
--compactCompact JSON output (no whitespace)
--json-indent NJSON indentation width (default: 2 for TTY, compact otherwise)
--only TYPESComma-separated block types to include
--exclude TYPESComma-separated block types to exclude
--fields FIELDSComma-separated field names to keep
--with-metaAdd __start_line__ / __end_line__ metadata
--with-commentsInclude comments as __comments__ / __inline_comments__ object lists
--wrap-objectsWrap object values as inline HCL2
--wrap-tuplesWrap tuple values as inline HCL2
--no-explicit-blocksDisable __is_block__ markers
--no-preserve-heredocsConvert heredocs to plain strings
--force-parensForce parentheses around all operations
--no-preserve-scientificConvert scientific notation to standard floats
--strip-string-quotesStrip surrounding double-quotes from string values (breaks round-trip)
--versionShow version and exit

Note on --strip-string-quotes: This removes the surrounding "..." from serialized string values (e.g. "\"my-bucket\"" becomes "my-bucket"). Useful for read-only workflows but round-trip through jsontohcl2 is not supported with this option, as the parser cannot distinguish bare strings from expressions.

jsontohcl2

Convert JSON files to HCL2. Accepts files, directories, glob patterns, or stdin (default when no args given).

jsontohcl2 output.json                                    # single file to stdout
jsontohcl2 output.json -o main.tf                        # single file to output file
jsontohcl2 output/ -o terraform/                         # directory conversion
jsontohcl2 --diff original.tf modified.json              # preview changes as unified diff
jsontohcl2 --semantic-diff original.tf modified.json     # semantic-only diff (ignores formatting)
jsontohcl2 --semantic-diff original.tf --diff-json m.json  # semantic diff as JSON
jsontohcl2 --dry-run file.json                           # convert without writing
jsontohcl2 --fragment -                                  # attribute snippets from stdin
echo '{"x": 1}' | jsontohcl2                            # stdin (no args needed)

Exit codes: 0 = success, 1 = JSON/encoding parse error, 2 = bad HCL structure, 4 = I/O error, 5 = differences found (--diff / --semantic-diff).

Flags:

FlagDescription
-o, --outputOutput path (file for single input, directory for multiple)
-sSkip un-parsable files
-q, --quietSuppress progress output on stderr
--diff ORIGINALShow unified diff against ORIGINAL file (exit 0 = identical, 5 = differs)
--semantic-diff ORIGINALShow semantic-only diff against ORIGINAL (ignores formatting differences)
--diff-jsonOutput diff results as JSON (works with --diff and --semantic-diff)
--dry-runConvert and print to stdout without writing files
--fragmentTreat input as attribute dict, not full HCL document (see note below)
--indent NIndentation width (default: 2)
--colon-separatorUse : instead of = in object elements
--no-trailing-commaOmit trailing commas in object elements
--heredocs-to-stringsConvert heredocs to plain strings
--strings-to-heredocsConvert strings with escaped newlines to heredocs
--no-open-empty-blocksCollapse empty blocks to a single line
--no-open-empty-objectsCollapse empty objects to a single line
--open-empty-tuplesExpand empty tuples across multiple lines
--no-alignDisable vertical alignment of attributes and object elements
--versionShow version and exit

Note on --fragment string format: --fragment uses python-hcl2's standard JSON format, where HCL string values carry inner quotes. To produce the HCL attribute name = "test", the JSON value must be "\"test\"" (escaped inner quotes). A plain JSON string like "test" becomes the bare identifier test. This is the same convention used by hcl2tojson output — so piping hcl2tojson output into jsontohcl2 --fragment works correctly. Numbers, booleans, and expressions (var.foo, local.name) do not need quoting.

hq

Query HCL2 files by structure, with optional Python expressions.

hq 'resource.aws_instance.main.ami' main.tf
hq 'variable[*]' variables.tf --json

For the full guide, see hq Reference.

Next Steps