The Developer's Guide to HCL, Part 2: Mastering the Syntax
Learn the core building blocks of HCL syntax: arguments, blocks, identifiers, comments, and the primitive and complex data types used in modern IaC.
A solid understanding of HCL's fundamental syntax is essential for effectively writing configurations. HCL is designed to be human-readable and writable, built around a few key constructs. More in Part 1.
HCL Arguments and Blocks
HCL syntax is primarily built around two key elements: arguments and blocks.
- Arguments: An argument assigns a value to a particular name. The syntax is
name = expression
.image_id = "ami-0c55b31ad54g39a5b"
is an example whereimage_id
is the argument name and"ami-0c55b31ad54g39a5b"
is its string value.- The context where an argument appears (e.g., within a specific resource block) determines the valid value types and whether the argument is required or optional. Many arguments accept arbitrary expressions, allowing values to be literal or programmatically generated.
- Blocks: A block is a container for other content, including arguments and potentially other nested blocks, creating a hierarchical configuration structure.
- A block has a type (e.g.,
resource
,source
,variable
) and can have one or more labels. For example, inresource "aws_instance" "web" {... }
,resource
is the block type, and"aws_instance"
and"web"
are labels. - The block type defines how many labels are required. For instance, a
source
block in Packer might expect two labels (e.g.,source "amazon-ebs" "example"
). Some block types may require no labels. - The block body, enclosed in curly braces (
{
and}
), contains the arguments and nested blocks that define the configuration object. - HCL uses a limited number of top-level block types (blocks that can appear outside any other block). Most features in tools like Packer and Terraform (resources, input variables, data sources, etc.) are implemented as top-level blocks.
- A block has a type (e.g.,
This structure of arguments and blocks allows for clear and organized configuration files.
HCL Identifiers
Identifiers are names used for arguments, block types, and most tool-specific constructs like resources and variables.
- They can contain letters, digits, underscores (
_
), and hyphens (-
). - The first character of an identifier must not be a digit to avoid ambiguity with literal numbers.
- Packer, for example, implements the Unicode identifier syntax, extended to include the ASCII hyphen character
-
.
Consistent and descriptive identifiers are crucial for readability and maintainability.
Data Types in HCL: Primitive and Complex
HCL supports several data types for argument values and expressions. These can be broadly categorized into primitive and complex types.
- Primitive Types:
string
: A sequence of Unicode characters representing text, e.g.,"hello"
or"${var.name}-app"
. Strings can be defined with double quotes or using a "heredoc" syntax for multi-line strings.number
: A numeric value, which can represent both whole numbers (e.g.,15
) and fractional values (e.g.,6.283185
).bool
: A boolean value, which can be eithertrue
orfalse
.
- Complex Types (Collections):
list
(ortuple
): An ordered sequence of values, identified by consecutive zero-based integer indices, e.g.,["us-west-1a", "us-west-1c"]
or[var.a, var.b]
. Elements can be of mixed types.map
(orobject
): A collection of key-value pairs, where keys are strings and values can be of any type, e.g.,{ name = "Mabel", age = 52 }
or{ type = "t2.micro", disk_size = 50 }
.set
: An unordered collection of unique values where all elements must be of the same type.for_each
requires sets when iterating over a flat list of strings.
null
: A special value representing the absence or omission of a value. If an argument is set tonull
, the HCL-consuming application (like Terraform or Nomad) typically behaves as if the argument was not set at all, potentially using a default value or raising an error if the argument is mandatory. This is particularly useful in conditional expressions.
HCL-consuming applications like Nomad and Terraform often perform automatic type conversion where possible. For example, if an argument expects a string but is given a number, the number will typically be converted to its string representation.
Table 2: Common HashiCorp HCL Data Types
Type | Description | Example Literal(s) | Key Characteristics |
---|---|---|---|
| Sequence of Unicode characters (text) |
| Used for names, descriptions, paths. Supports interpolation and heredocs. |
| Numeric value (integer or fractional) |
| Used for counts, sizes, ports, etc. |
| Boolean value |
| Used for flags, conditional logic. |
| Ordered sequence of values |
| Zero-indexed. Elements can be of mixed types. |
| Collection of key-value pairs (keys are strings) |
| Keys must be unique. Values can be of mixed types. |
| A | (Used in type constraints for variables) | Defines expected attributes and their types for a structured variable. |
| Unordered collection of unique values |
| All elements must be of the same type. Required by |
| Represents absence or omission of a value |
| Makes an argument behave as if it wasn't set. |
Documenting Your HCL Code
Comments are crucial for explaining the intent and logic behind HCL configurations. HCL supports three syntaxes:
/*...*/
(Slash-star and star-slash): Delimiters for a multi-line comment.
/*
This is a multi-line comment.
*/
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16" /* Inline comment */
}
//
(Double slash): Also begins a single-line comment. terraform fmt
often converts this to #
.
// This is also a single-line comment
variable "region" {
type = string // Describes the AWS region
}
#
(Hash symbol): Begins a single-line comment. This is the idiomatic and recommended style.
# This is a single-line comment
resource "aws_instance" "example" { # This comment is at the end of a line
ami = "ami-12345"
}
Character Encoding and Line Endings
To ensure consistency and avoid parsing issues:
- Character Encoding: HCL configuration files must always be UTF-8 encoded.
- Line Endings: HCL accepts Unix-style (LF) or Windows-style (CRLF) line endings. However, the idiomatic style is Unix (LF), and
terraform fmt
will typically enforce this.
Next up, common issues:

Key Sources
- HCL Native Syntax Specification: https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md - The formal specification for HCL's syntax, including comments and identifiers.
- Terraform Documentation - Types and Values: https://developer.hashicorp.com/terraform/language/values/types - The definitive guide to data types in Terraform's implementation of HCL.
- Packer Documentation - HCL Syntax: https://developer.hashicorp.com/packer/docs/templates/hcl_templates/syntax - Details on syntax elements like blocks, arguments, and identifiers as used in Packer.