---
description: >-
  For historical reasons, certain arguments within resource blocks can use
  either

  block or attribute syntax.
---

# Attributes as Blocks

:::note
This page is an appendix to the OpenTofu documentation. Most users do not need to know the full details of this behavior.
:::

## Summary

Many resource types use repeatable nested blocks to manage collections of
sub-objects related to the primary resource.

Rarely, some resource types _also_ support an argument with the same name as a
nested block type, and will purge any sub-objects of that type if that argument
is set to an empty list (`<ATTR> = []`).

Most users do not need to know any further details of this "nested block or
empty list" behavior. However, read further if you need to:

- Use OpenTofu's [JSON syntax](./syntax/json.mdx) with this
  type of resource.
- Create a reusable module that wraps this type of resource.

## Details

The language makes a distinction between
[argument syntax and nested block syntax](syntax/configuration.mdx#arguments-and-blocks)
within blocks:

- Argument syntax sets a named argument for the containing object. If the
  attribute has a default value then an explicitly-specified value entirely
  overrides that default.

- Nested block syntax represents a related child object of the container that
  has its own set of arguments. Where multiple such objects are possible, multiple
  blocks of the same type can be present. If the nested attributes themselves
  have default values, they are honored for each nested block separately,
  merging in with any explicitly-defined arguments.

The distinction between these is particularly important for
[JSON syntax](syntax/json.mdx)
because the same primitive JSON constructs (lists and objects) will be
interpreted differently depending on whether a particular name is an argument
or a nested block type.

## Defining a Fixed Object Collection Value

When working with resource type arguments that behave in this way, it is valid
and we recommend the use of nested block syntax whenever defining a fixed
collection of objects:

```hcl
example {
  foo = "bar"
}
example {
  foo = "baz"
}
```

The above implicitly specifies a two-element list of objects assigned to the
`example` argument, treating it as if it were a nested block type.

If you need to explicitly call for zero `example` objects, you must use the
argument syntax with an empty list:

```hcl
example = []
```

These two forms cannot be mixed; there cannot be both explicitly zero `example`
objects and explicit single `example` blocks declared at the same time.

For true nested blocks where this special behavior does not apply, assigning
`[]` using argument syntax is not valid. The normal way to specify zero objects
of a type is to write no nested blocks at all.

## Arbitrary Expressions with Argument Syntax

Although we recommend using block syntax for simple cases for readability, the
names that work in this mode _are_ defined as arguments, and so it is possible
to use argument syntax to assign arbitrary dynamic expressions to them, as
long as the expression has the expected result type:

```hcl
example = [
  for name in var.names: {
    foo = name
  }
]
```

```hcl
# Not recommended, but valid: a constant list-of-objects expression
example = [
  {
    foo = "bar"
  },
  {
    foo = "baz"
  },
]
```

Because of the rule that argument declarations like this fully override any
default value, when creating a list-of-objects expression directly the usual
handling of optional arguments does not apply, so all of the arguments must be
assigned a value, even if it's an explicit `null`:

```hcl
example = [
  {
    # Cannot omit foo in this case, even though it would be optional in the
    # nested block syntax.
    foo = null
  },
]
```

If you are writing a reusable module that allows callers to pass in a list of
objects to assign to such an argument, you may wish to use the `merge` function
to populate any attributes the user didn't explicitly set, in order to give
the module user the effect of optional arguments:

```hcl
example = [
  for ex in var.examples: merge({
    foo = null # (or any other suitable default value)
  }, ex)
]
```

For the arguments that use the attributes-as-blocks usage mode, the above is
a better pattern than using
[`dynamic` blocks](expressions/dynamic-blocks.mdx)
because the case where the
caller provides an empty list will result in explicitly assigning an empty
list value, rather than assigning no value at all and thus retaining and
ignoring any existing objects. `dynamic` blocks are required for
dynamically-generating _normal_ nested blocks, though.

## In JSON syntax

Arguments that use this special mode are specified in JSON syntax always using
the [JSON expression mapping](syntax/json.mdx#expression-mapping)
to produce a list of objects.

The interpretation of these values in JSON syntax is, therefore, equivalent
to that described under _Arbitrary Expressions with Argument Syntax_ above,
but expressed in JSON syntax instead.

Due to the ambiguity of the JSON syntax, there is no way to distinguish based
on the input alone between argument and nested block usage, so the JSON syntax
cannot support the nested block processing mode for these arguments. This is,
unfortunately, one necessary concession on the equivalence between native syntax
and JSON syntax made pragmatically for compatibility with existing provider
design patterns. Providers may phase out such patterns in future major releases.
