Querying HCL (Python API)

April 7, 2026 · View on GitHub

The query system lets you navigate HCL documents by structure rather than serializing to dicts. This page covers the Python API; for the hq CLI tool, see hq Reference.

Quick Start

import hcl2

doc = hcl2.query('resource "aws_instance" "main" { ami = "abc-123" }')

for block in doc.blocks("resource"):
    print(block.block_type, block.name_labels)
    ami = block.attribute("ami")
    if ami:
        print(f"  ami = {ami.value}")

You can also parse from a file:

from hcl2.query import DocumentView

doc = DocumentView.parse_file("main.tf")

DocumentView

The entry point for queries. Wraps a StartRule.

doc = DocumentView.parse(text)         # from string
doc = DocumentView.parse_file("main.tf")  # from file
doc = hcl2.query(text)                 # convenience alias
doc = hcl2.query(open("main.tf"))      # also accepts file objects
Method / PropertyReturnsDescription
bodyBodyViewThe document body
blocks(block_type?, *labels)List[BlockView]Blocks matching type and optional labels
attributes(name?)List[AttributeView]Attributes, optionally filtered by name
attribute(name)AttributeView | NoneSingle attribute by name

BodyView

Wraps a BodyRule. Same filtering methods as DocumentView.

BlockView

Wraps a BlockRule.

block = doc.blocks("resource", "aws_instance")[0]
block.block_type    # "resource"
block.labels        # ["resource", "aws_instance", "main"]
block.name_labels   # ["aws_instance", "main"]
block.body          # BodyView
Property / MethodReturnsDescription
block_typestrFirst label (the block type name)
labelsList[str]All labels as plain strings
name_labelsList[str]Labels after the block type (labels[1:])
bodyBodyViewThe block body
blocks(...)List[BlockView]Nested blocks (delegates to body)
attributes(...)List[AttributeView]Nested attributes (delegates to body)
attribute(name)AttributeView | NoneSingle nested attribute

AttributeView

Wraps an AttributeRule.

attr = doc.attribute("ami")
attr.name        # "ami"
attr.value       # '"abc-123"' (serialized Python value)
attr.value_node  # NodeView over the expression

Container Views

TupleView

Wraps a TupleRule. Access via find_all or by navigating to a tuple-valued attribute.

from hcl2.query.containers import TupleView
from hcl2.walk import find_first
from hcl2.rules.containers import TupleRule

doc = DocumentView.parse('x = [1, 2, 3]\n')
node = find_first(doc.attribute("x").raw, TupleRule)
tv = TupleView(node)
len(tv)          # 3
tv[0]            # NodeView for the first element
tv.elements      # List[NodeView]

ObjectView

Wraps an ObjectRule.

from hcl2.query.containers import ObjectView
from hcl2.rules.containers import ObjectRule

node = find_first(doc.attribute("tags").raw, ObjectRule)
ov = ObjectView(node)
ov.keys           # ["Name", "Env"]
ov.get("Name")    # NodeView for the value
ov.entries        # List[Tuple[str, NodeView]]

Expression Views

ForTupleView / ForObjectView

Wraps ForTupleExprRule / ForObjectExprRule.

from hcl2.query.for_exprs import ForTupleView
from hcl2.rules.for_expressions import ForTupleExprRule

doc = DocumentView.parse('x = [for item in var.list : item]\n')
node = find_first(doc.raw, ForTupleExprRule)
fv = ForTupleView(node)
fv.iterator_name         # "item"
fv.second_iterator_name  # None (or "v" for "k, v in ...")
fv.iterable              # NodeView
fv.value_expr            # NodeView
fv.has_condition         # bool
fv.condition             # NodeView | None

ForObjectView adds key_expr and has_ellipsis.

ConditionalView

Wraps a ConditionalRule (ternary condition ? true : false).

from hcl2.query.expressions import ConditionalView
from hcl2.rules.expressions import ConditionalRule

doc = DocumentView.parse('x = var.enabled ? "on" : "off"\n')
node = find_first(doc.raw, ConditionalRule)
cv = ConditionalView(node)
cv.condition   # NodeView over the condition expression
cv.true_val    # NodeView over the true branch
cv.false_val   # NodeView over the false branch

FunctionCallView

Wraps a FunctionCallRule.

from hcl2.query.functions import FunctionCallView
from hcl2.rules.functions import FunctionCallRule

doc = DocumentView.parse('x = length(var.list)\n')
node = find_first(doc.raw, FunctionCallRule)
fv = FunctionCallView(node)
fv.name           # "length"
fv.args           # List[NodeView]
fv.has_ellipsis   # bool

Common NodeView Methods

All view classes inherit from NodeView:

Method / PropertyReturnsDescription
rawLarkElementThe underlying IR node
parent_viewNodeView | NoneView over the parent node
to_hcl()strReconstruct this subtree as HCL text
to_dict(options?)AnySerialize to a Python value
find_all(rule_type)List[NodeView]Find descendants by rule class
find_by_predicate(fn)List[NodeView]Find descendants where fn(view) is truthy
walk_semantic()List[NodeView]All semantic descendant nodes
walk_rules()List[NodeView]All rule descendant nodes

Tree Walking Primitives

The hcl2.walk module provides free functions for traversing the IR tree directly (without view wrappers):

from hcl2.walk import walk, walk_rules, walk_semantic, find_all, find_first, ancestors
from hcl2.rules.base import AttributeRule

tree = hcl2.parses('x = 1\ny = 2\n')

# All nodes depth-first (including tokens)
for node in walk(tree):
    print(node)

# Only LarkRule nodes
for rule in walk_rules(tree):
    print(rule)

# Only semantic rules (skip NewLineOrCommentRule)
for rule in walk_semantic(tree):
    print(rule)

# Find specific rule types
attrs = list(find_all(tree, AttributeRule))
first_attr = find_first(tree, AttributeRule)

# Walk up the parent chain
for parent in ancestors(first_attr):
    print(parent)

Next Steps