Resource Collections

Creating multiple resources with for_each.

The resources (plural) block creates multiple resources by iterating over a collection.

Syntax

resources <base-name> {
  locals { ... }          # optional: collection-scoped locals

  for_each = <expression> # required: list, set, or map to iterate over

  name = <expression>     # optional: how to generate each crossplane name
                          # default: "${self.basename}-${each.key}"

  template {              # required: the template for each resource
    locals { ... }        # optional: template-scoped locals
    body = { ... }        # required: the Kubernetes manifest
    composite status { }  # optional
    composite connection { } # optional
    ready { }             # optional
  }
}

Example

resources additional_buckets {
  locals {
    params   = req.composite.spec.parameters
    suffixes = params.suffixes
  }

  for_each = suffixes

  template {
    locals {
      resourceName = "${req.composite.metadata.name}-${self.name}"
    }

    body = {
      apiVersion = "s3.aws.upbound.io/v1beta1"
      kind       = "Bucket"
      metadata = {
        name = resourceName
      }
      spec = {
        forProvider = {
          forceDestroy = true
          region       = params.region
        }
      }
    }
  }
}

The for_each Attribute

Must evaluate to a list, set, or map.

The name Attribute

Controls how the crossplane name is generated for each resource in the collection. Defaults to "${self.basename}-${each.key}".

resources my-buckets {
  for_each = ["prod", "staging"]
  name = "${self.basename}-${each.value}"  # produces "my-buckets-prod", "my-buckets-staging"

  template {
    body = { ... }
  }
}

The self Variable

Inside a resources block, self provides collection-level metadata and observed state:

ExpressionWhere availableTypeDescription
self.basenamename, templatestringThe name given to the resources block
self.nametemplate onlystringThe generated crossplane name for the current resource
self.resourcesname, templatelist or incompleteThe observed resource collection
self.connectionsname, templatelist or incompleteConnection details of the collection

The each Variable

Inside a resources block, each provides the current iterator state. It is available in both the name expression and the template block.

ExpressionDescription
each.keyIndex for lists, map key for maps, value for sets
each.valueValue at the current position

The meaning of each.key and each.value depends on the collection type passed to for_each:

Collection typeeach.keyeach.value
ListIndex (0, 1, 2, …)Value at that index
MapMap keyMap value
SetThe value itselfThe value itself

The template Block

The template block has exactly the same semantics as a resource block. Anything you can do in a resource block is allowed inside template: locals, body, composite status, composite connection, ready.

Using range() for Counted Resources

To create a fixed number of resources (like Terraform’s count), use range():

resources my-buckets {
  locals {
    numBuckets = try(req.composite.spec.parameters.numBuckets, 1)
  }

  for_each = range(numBuckets)

  template {
    locals {
      name = "${req.composite.metadata.name}-bucket-${each.value}"
    }
    body = {
      apiVersion = "s3.aws.upbound.io/v1beta1"
      kind       = "Bucket"
      metadata   = { name = name }
      spec       = { forProvider = { region = req.composite.spec.parameters.region } }
    }
  }
}
Last modified March 7, 2026: docs (235cb74)