---
page_title: HCL expressions reference
description: |-
  HCL expressions provide access to data exported by data sources and transforms and combines the data into other values. Learn how to use HCL expressions in Packer templates.
---

# HCL expressions reference

This topic provides reference information about expressions you can use in HCL templates for Packer.

## Introduction

_Expressions_ are used to refer to or compute values within a configuration.
The simplest expressions are just literal values, like `"hello"` or `5`, but
HCL also allows more complex expressions such as references to data exported by
sources, arithmetic, conditional evaluation, and a number of built-in
functions.

Expressions can be used in a number of places in HCL, but some contexts limit
which expression constructs are allowed, such as requiring a literal value of a
particular type or forbidding. Each language feature's documentation describes
any restrictions it places on expressions.

The rest of this page describes all of the features of Packer's
expression syntax.

## Types and Values

The result of an expression is a _value_. All values have a _type_, which
dictates where that value can be used and what transformations can be
applied to it.

HCL uses the following types for its values:

- `string`: a sequence of Unicode characters representing some text, like
  `"hello"`.
- `number`: a numeric value. The `number` type can represent both whole
  numbers like `15` and fractional values like `6.283185`.
- `bool`: either `true` or `false`. `bool` values can be used in conditional
  logic.
- `list` (or `tuple`): a sequence of values, like
  `["us-west-1a", "us-west-1c"]`. Elements in a list or tuple are identified by
  consecutive whole numbers, starting with zero.
- `map` (or `object`): a group of values identified by named labels, like
  `{name = "Mabel", age = 52}`.

Strings, numbers, and bools are sometimes called _primitive types._
Lists/tuples and maps/objects are sometimes called _complex types,_ _structural
types,_ or _collection types._

Finally, there is one special value that has _no_ type:

- `null`: a value that represents _absence_ or _omission._ If you set an
  argument of a source or module to `null`, Packer behaves as though you
  had completely omitted it — it will use the argument's default value if it has
  one, or raise an error if the argument is mandatory. `null` is most useful in
  conditional expressions, so you can dynamically omit an argument if a
  condition isn't met.

### Advanced Type Details

In most situations, lists and tuples behave identically, as do maps and objects.
Whenever the distinction isn't relevant, the Packer documentation uses each
pair of terms interchangeably (with a historical preference for "list" and
"map").

However, plugin authors should understand the differences between these similar
types (and the related `set` type), since they offer different ways to restrict
the allowed values for input variables and source arguments.

### Type Conversion

Expressions are most often used to set values for arguments. In these cases,
the argument has an expected type and the given expression must produce a value
of that type.

Where possible, Packer automatically converts values from one type to
another in order to produce the expected type. If this isn't possible, Packer
will produce a type mismatch error and you must update the configuration with a
more suitable expression.

Packer automatically converts number and bool values to strings when needed.
It also converts strings to numbers or bools, as long as the string contains a
valid representation of a number or bool value.

- `true` converts to `"true"`, and vice-versa
- `false` converts to `"false"`, and vice-versa
- `15` converts to `"15"`, and vice-versa

## Literal Expressions

A _literal expression_ is an expression that directly represents a particular
constant value. Packer has a literal expression syntax for each of the value
types described above:

- Strings are usually represented by a double-quoted sequence of Unicode
  characters, `"like this"`. There is also a "heredoc" syntax for more complex
  strings. String literals are the most complex kind of literal expression in
  Packer, and have additional documentation on this page:
  - See [String Literals](#string-literals) below for information about escape
    sequences and the heredoc syntax.
  - See [String Templates](#string-templates) below for information about
    interpolation and template directives.
- Numbers are represented by unquoted sequences of digits with or without a
  decimal point, like `15` or `6.283185`.
- Bools are represented by the unquoted symbols `true` and `false`.
- The null value is represented by the unquoted symbol `null`.
- Lists/tuples are represented by a pair of square brackets containing a
  comma-separated sequence of values, like `["a", 15, true]`.

  List literals can be split into multiple lines for readability, but always
  require a comma between values. A comma after the final value is allowed,
  but not required. Values in a list can be arbitrary expressions.

- Maps/objects are represented by a pair of curly braces containing a series of
  `<KEY> = <VALUE>` pairs:

  ```hcl
  {
    name = "John"
    age  = 52
  }
  ```

  Key/value pairs can be separated by either a comma or a line break. Values
  can be arbitrary expressions. Keys are strings; they can be left unquoted if
  they are a valid [identifier](/packer/docs/templates/hcl_templates/syntax#identifiers), but must be quoted
  otherwise. You can use a non-literal expression as a key by wrapping it in
  parentheses, like `(var.business_unit_tag_name) = "SRE"`.

## References to Named Values

Packer makes one named values available.

The following named values are available:

- `source.<SOURCE TYPE>.<NAME>` is an object representing a
  [source](/packer/docs/templates/hcl_templates/blocks/source) of the given type
  and name.

### Available Functions

For a full list of available functions, see [the function
reference](/packer/docs/templates/hcl_templates/functions).

## Conditional Expressions

A conditional expression uses the value of a boolean expression to select one of
two values. This is a compact way to express `if-then-else` logic inside an
expression.

The syntax for a conditional expression is:

```hcl
condition ? true_val : false_val
```

If `condition` is `true`, the expression returns `true_val`. If `condition` is
`false`, it returns `false_val`. The `true_val` and `false_val` arguments must
be of compatible types.

### Examples

Here are some examples of how to use conditional expressions.

1.  Setting a Default Value

    You can use a conditional to provide a default value for a variable that
    can also be customized.

    ```hcl
    locals {
      # Set the region to the value of var.region if it is not empty.
      # Otherwise, use a default value.
      region = var.region != "" ? var.region : "us-east-1"
    }
    ```

2.  Dynamically Omitting an Argument

    You can use a `null` value to dynamically omit an argument from a source.
    This is a common pattern to effectively "turn off" or prevent an argument
    from being set.

    ```hcl
    variable "use_http" {
      type        = bool
      description = "Whether to use HTTP for file transfer."
      default     = true
    }

    source "example" "foo" {
      # The http_directory argument is only set if var.use_http is true.
      # If false, http_directory will be unset.
      http_directory = var.use_http ? "/path/to/files" : null
    }
    ```

    This can also be used to set one of two mutually exclusive arguments:

    ```hcl
    source "builder" "example" {
      # If var.use_http is true, http_content is set and cd_content is null.
      # If var.use_http is false, http_content is null and cd_content is set.
      http_content = var.use_http ? local.http_files : null
      cd_content   = !var.use_http ? local.cd_files : null
    }
    ```

### Readability and Recommended Practices

While powerful, conditional expressions can lead to complex or hard-to-read
configurations if not used carefully.

  * **Prioritize Clarity:** When a conditional expression becomes very long or
    involves multiple nested conditions, consider breaking it down.

  * **Leverage `locals` for Complexity:** For more intricate conditional logic,
    define the result in a `local` variable. This improves readability in the
    main `source` or `provisioner` blocks where the `local` is consumed.

    **Example:**

    ```hcl
    locals {
      # Determine the kickstart file based on the build environment.
      kickstart_config_path = var.environment == "production" ?
                              "config/prod-ks.cfg" :
                              (var.environment == "staging" ?
                               "config/stage-ks.cfg" :
                               "config/dev-ks.cfg")
    }

    source "builder" "example" {
      # ...
      # Use the pre-determined kickstart path.
      http_content = file(local.kickstart_config_path)
      # ...
    }
    ```

  * **Use `null` to Omit Arguments:** As demonstrated in the examples,
    using `null` is the idiomatic way to prevent an argument from being set or
    to effectively "turn off" a setting based on a condition.

## `for` Expressions

A _`for` expression_ creates a complex type value by transforming
another complex type value. Each element in the input value
can correspond to either one or zero values in the result, and an arbitrary
expression can be used to transform each input element into an output element.

For example, if `var.list` is a list of strings, then the following expression
produces a list of strings with all-uppercase letters:

```hcl
[for s in var.list : upper(s)]
```

This `for` expression iterates over each element of `var.list`, and then
evaluates the expression `upper(s)` with `s` set to each respective element.
It then builds a new tuple value with all of the results of executing that
expression in the same order.

The type of brackets around the `for` expression decide what type of result
it produces. The above example uses `[` and `]`, which produces a tuple. If
`{` and `}` are used instead, the result is an object, and two result
expressions must be provided separated by the `=>` symbol:

```hcl
{for s in var.list : s => upper(s)}
```

This expression produces an object whose attributes are the original elements
from `var.list` and their corresponding values are the uppercase versions.

A `for` expression can also include an optional `if` clause to filter elements
from the source collection, which can produce a value with fewer elements than
the source:

```text
[for s in var.list : upper(s) if s != ""]
```

The source value can also be an object or map value, in which case two
temporary variable names can be provided to access the keys and values
respectively:

```text
[for k, v in var.map : length(k) + length(v)]
```

Finally, if the result type is an object (using `{` and `}` delimiters) then
the value result expression can be followed by the `...` symbol to group
together results that have a common key:

```text
{for s in var.list : substr(s, 0, 1) => s... if s != ""}
```

## Splat Expressions

A _splat expression_ provides a more concise way to express a common operation
that could otherwise be performed with a `for` expression.

If `var.list` is a list of objects that all have an attribute `id`, then a list
of the IDs could be produced with the following `for` expression:

```hcl
[for o in var.list : o.id]
```

This is equivalent to the following _splat expression:_

```hcl
var.list[*].id
```

The special `[*]` symbol iterates over all of the elements of the list given to
its left and accesses from each one the attribute name given on its right. A
splat expression can also be used to access attributes and indexes from lists
of complex types by extending the sequence of operations to the right of the
symbol:

```hcl
var.list[*].interfaces[0].name
```

The above expression is equivalent to the following `for` expression:

```hcl
[for o in var.list : o.interfaces[0].name]
```

Splat expressions are for lists only (and thus cannot be used [to reference
resources created with
`for_each`](/terraform/docs/configuration/resources#referring-to-instances), which
are represented as maps). However, if a splat expression is applied to a value
that is _not_ a list or tuple then the value is automatically wrapped in a
single-element list before processing.

For example, `var.single_object[*].id` is equivalent to
`[var.single_object][*].id`, or effectively `[var.single_object.id]`. This
behavior is not interesting in most cases, but it is particularly useful when
referring to resources that may or may not have `count` set, and thus may or
may not produce a tuple value:

```hcl
aws_instance.example[*].id
```

The above will produce a list of IDs whether `aws_instance.example` has `count`
set or not, avoiding the need to revise various other expressions in the
configuration when a particular resource switches to and from having `count`
set.

## `dynamic` blocks

Within top-level block constructs like [source](/packer/docs/templates/hcl_templates/blocks/source), expressions
can usually be used only when assigning a value to an argument using the `name = expression` or `key = expression` form.
This covers many uses, but some source types include repeatable _nested blocks_ in their arguments, which do not accept expressions:

```hcl
source "amazon-ebs" "example" {
  name = "pkr-test-name" # can use expressions here

  tag {
    # but the "tag" block is always a literal block
  }
}
```

You can dynamically construct repeatable nested blocks like `tag` using a
special `dynamic` block type, which is supported in top-level block constructs, example:

```hcl
locals {
  standard_tags = {
    Component   = "user-service"
    Environment = "production"
  }
}

source "amazon-ebs" "example" {
  # ...

  tag {
    key                 = "Name"
    value               = "example-asg-name"
  }

  dynamic "tag" {
    for_each = local.standard_tags

    content {
      key                 = tag.key
      value               = tag.value
    }
  }
}
```

A `dynamic` block acts much like a `for` expression, but produces nested blocks
instead of a complex typed value. It iterates over a given complex value, and
generates a nested block for each element of that complex value.

- The label of the dynamic block (`"tag"` in the example above) specifies
  what kind of nested block to generate.
- The `for_each` argument provides the complex value to iterate over.
- The `iterator` argument (optional) sets the name of a temporary variable
  that represents the current element of the complex value. If omitted, the name
  of the variable defaults to the label of the `dynamic` block (`"tag"` in
  the example above).
- The `labels` argument (optional) is a list of strings that specifies the block
  labels, in order, to use for each generated block. You can use the temporary
  iterator variable in this value.
- The nested `content` block defines the body of each generated block. You can
  use the temporary iterator variable inside this block.

Since the `for_each` argument accepts any collection or structural value,
you can use a `for` expression or splat expression to transform an existing
collection.

The iterator object (`tag` in the example above) has two attributes:

- `key` is the map key or list element index for the current element. If the
  `for_each` expression produces a _set_ value then `key` is identical to
  `value` and should not be used.
- `value` is the value of the current element.

A `dynamic` block can only generate arguments for nested blocks that belong to
the source type, data source, or provisioner being configured.

The `for_each` value must be a map or set with one element per desired nested
block. If you need to declare resource instances based on a nested data
structure or combinations of elements from multiple data structures you can use
expressions and functions to derive a suitable value. For some common examples
of such situations, see the
[`flatten`](/packer/docs/templates/hcl_templates/functions/collection/flatten) and
[`setproduct`](/packer/docs/templates/hcl_templates/functions/collection/setproduct)
functions.

### Best Practices for `dynamic` Blocks

Overuse of `dynamic` blocks can make configuration hard to read and maintain,
so we recommend using them only when you need to hide details in order to build
a clean user interface for a re-usable code. Always write nested blocks out
literally where possible.

## String Literals

HCL has two different syntaxes for string literals. The
most common is to delimit the string with quote characters (`"`), like
`"hello"`. In quoted strings, the backslash character serves as an escape
sequence, with the following characters selecting the escape behavior:

| Sequence     | Replacement                                                                   |
| ------------ | ----------------------------------------------------------------------------- |
| `\n`         | Newline                                                                       |
| `\r`         | Carriage Return                                                               |
| `\t`         | Tab                                                                           |
| `\"`         | Literal quote (without terminating the string)                                |
| `\\`         | Literal backslash                                                             |
| `\uNNNN`     | Unicode character from the basic multilingual plane (NNNN is four hex digits) |
| `\UNNNNNNNN` | Unicode character from supplementary planes (NNNNNNNN is eight hex digits)    |

The alternative syntax for string literals is the so-called Here Documents or
"heredoc" style, inspired by Unix shell languages. This style allows multi-line
strings to be expressed more clearly by using a custom delimiter word on a line
of its own to close the string:

```hcl
<<EOF
hello
world
EOF
```

The `<<` marker followed by any identifier at the end of a line introduces the
sequence. Packer then processes the following lines until it finds one that
consists entirely of the identifier given in the introducer. In the above
example, `EOF` is the identifier selected. Any identifier is allowed, but
conventionally this identifier is in all-uppercase and begins with `EO`, meaning
"end of". `EOF` in this case stands for "end of text".

The "heredoc" form shown above requires that the lines following be flush with
the left margin, which can be awkward when an expression is inside an indented
block:

```hcl
block {
  value = <<EOF
hello
world
EOF
}
```

To improve on this, Packer also accepts an _indented_ heredoc string variant
that is introduced by the `<<-` sequence:

```hcl
block {
  value = <<-EOF
  hello
    world
  EOF
}
```

In this case, Packer analyses the lines in the sequence to find the one
with the smallest number of leading spaces, and then trims that many spaces
from the beginning of all of the lines, leading to the following result:

```text
hello
  world
```

Backslash sequences are not interpreted in a heredoc string expression.
Instead, the backslash character is interpreted literally.

In both quoted and heredoc string expressions, Packer supports template
sequences that begin with `${` and `%{`. These are described in more detail
in the following section. To include these sequences _literally_ without
beginning a template sequence, double the leading character: `$${` or `%%{`.

## String Templates

Within quoted and heredoc string expressions, the sequences `${` and `%{` begin
_template sequences_. Templates let you directly embed expressions into a string
literal, to dynamically construct strings from other values.

The following example demonstrates how a template sequence can be used to embed
the value of a variable into a string that can be used as script content:

```hcl
locals {
  packages = ["git", "curl", "vim"]

  install_packages = <<-EOF
    #!/bin/bash
    if [ ${length(local.packages)} -eq 0 ]; then
      echo "No packages to install."
      exit 1
    fi
    apt-get update
    %{ for package in local.packages ~}
    apt-get install -y ${package}
    %{ endfor ~}
  EOF
}

source "amazon-ebs" "example" {
  # ...
}

build {
  sources = ["source.amazon-ebs.example"]

  provisioner "shell" {
    inline = [local.install_packages]
  }
}
```

This can be tested using the `packer console` command:

```shell
$ packer source.pkr.hcl

> local.install_packages

> #!/bin/bash
if [ 3 -eq 0 ]; then
  echo "No packages to install."
  exit 1
fi
apt-get update
    apt-get install -y git
    apt-get install -y curl
    apt-get install -y vim
```
