> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ubiquex.io/llms.txt
> Use this file to discover all available pages before exploring further.

# for_each over an Object

> Iterate over a map to create resources with distinct keys and values.

A list gives you elements. An object gives you key-value pairs. When each resource in a set needs its own name and its own configuration value — buckets with different roles, SSM parameters with different names and values, security group rules with different ports — `for_each` over an object is the right tool. Both `each.key` (the map key) and `each.value` (the map value) are available inside the block, giving you access to both dimensions of each entry. Pulumi keys resources in state by `each.key`, which makes renames and removals predictable: changing a key destroys and recreates that resource, while changing only the value updates it in place.

## What you'll learn

* How `for_each` over an object provides both `each.key` and `each.value`
* The flat string map pattern — the most common form
* How Pulumi uses `each.key` to track resources in state

***

## Why this matters

<Info>
  Objects give you named, independently-addressable resources. Each `each.key` becomes a distinct Pulumi resource identity in state — you can remove one entry from the map without affecting the others, unlike list-based `for_each` where index shifts can cause unexpected destroys.
</Info>

***

## The source code

<CodeGroup>
  ```hcl main.iac theme={"theme":"css-variables"}
  # for_each over an object — each.key is the map key, each.value is the value.
  unit "aws_s3_bucket_v2" "bucket" {
    for_each = {
      assets  = "primary"
      logs    = "archive"
      uploads = "transient"
    }

    bucket = "${input.env}-${each.key}"

    tags = {
      Name = "${input.env}-${each.key}"
      Env  = input.env
      Role = each.value
    }
  }

  # Flat string map — SSM parameters keyed by name, valued by content.
  unit "aws_ssm_parameter" "config" {
    for_each = {
      log_level = "INFO"
      region    = "us-east-1"
      max_conns = "100"
    }

    name  = "/${input.env}/app/${each.key}"
    type  = "String"
    value = each.value
  }
  ```

  ```hcl inputs.iac theme={"theme":"css-variables"}
  input "env" {
    type    = "string"
    default = "dev"
  }
  ```

  ```hcl outputs.iac theme={"theme":"css-variables"}
  output "env" {
    value = input.env
  }
  ```

  ```hcl ubx.iac theme={"theme":"css-variables"}
  ubx {
    name    = "for-each-object"
    runtime = "typescript"
    stacks  = ["dev", "staging", "prod"]
  }

  backend "local" {
    path = ".ubx/state"
  }
  ```
</CodeGroup>

***

## How it works

<Steps>
  <Step title="The object is resolved at compile time">
    The object literal is `Resolved<T>` — all keys and values are known at compile time. ubx expands it into one `IRUnit` node per entry in the IR pass.
  </Step>

  <Step title="each.key and each.value are substituted per entry">
    For the buckets block: first iteration gives `each.key = "assets"`, `each.value = "primary"`; second gives `"logs"` / `"archive"`; and so on. Both are available anywhere inside the block body.
  </Step>

  <Step title="Pulumi keys by each.key">
    The generated code creates one resource per map entry, keyed by `each.key` in Pulumi state. Removing `"uploads"` from the map on the next apply destroys only that bucket — `"assets"` and `"logs"` are unaffected.
  </Step>
</Steps>

***

## What ubx generates

<CodeGroup>
  ```typescript index.ts theme={"theme":"css-variables"}
  // Generated by ubx — do not edit
  import * as pulumi from "@pulumi/pulumi";
  import * as aws from "@pulumi/aws";

  const config = new pulumi.Config();
  const env = config.get("env") || "dev";

  const bucketMap: Record<string, string> = {
      assets: "primary", logs: "archive", uploads: "transient",
  };
  const buckets = Object.fromEntries(
      Object.entries(bucketMap).map(([key, value]) => [key,
          new aws.s3.BucketV2(`bucket-${key}`, {
              bucket: `${env}-${key}`,
              tags: { Name: `${env}-${key}`, Env: env, Role: value },
          })
      ])
  );

  const ssmMap: Record<string, string> = {
      log_level: "INFO", region: "us-east-1", max_conns: "100",
  };
  const ssmParams = Object.fromEntries(
      Object.entries(ssmMap).map(([key, value]) => [key,
          new aws.ssm.Parameter(`config-${key}`, {
              name: `/${env}/app/${key}`,
              type: "String",
              value,
          })
      ])
  );

  export const envOut = env;
  ```

  ```python __main__.py theme={"theme":"css-variables"}
  # Generated by ubx — do not edit
  import pulumi
  import pulumi_aws as aws

  config = pulumi.Config()
  env = config.get("env") or "dev"

  bucket_map = {"assets": "primary", "logs": "archive", "uploads": "transient"}
  buckets = {
      key: aws.s3.BucketV2(f"bucket-{key}",
          bucket=f"{env}-{key}",
          tags={"Name": f"{env}-{key}", "Env": env, "Role": value},
      )
      for key, value in bucket_map.items()
  }

  ssm_map = {"log_level": "INFO", "region": "us-east-1", "max_conns": "100"}
  ssm_params = {
      key: aws.ssm.Parameter(f"config-{key}",
          name=f"/{env}/app/{key}",
          type="String",
          value=value,
      )
      for key, value in ssm_map.items()
  }

  pulumi.export("env", env)
  ```

  ```go main.go theme={"theme":"css-variables"}
  // Generated by ubx — do not edit
  package main

  import (
      "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/s3"
      "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/ssm"
      "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
      "github.com/pulumi/pulumi/sdk/v3/go/pulumi/config"
  )

  func main() {
      pulumi.Run(func(ctx *pulumi.Context) error {
          cfg := config.New(ctx, "")
          env := cfg.Get("env")
          if env == "" {
              env = "dev"
          }

          buckets := map[string]string{"assets": "primary", "logs": "archive", "uploads": "transient"}
          for key, value := range buckets {
              _, err := s3.NewBucketV2(ctx, "bucket-"+key, &s3.BucketV2Args{
                  Bucket: pulumi.Sprintf("%s-%s", env, key),
                  Tags:   pulumi.StringMap{"Name": pulumi.Sprintf("%s-%s", env, key), "Env": pulumi.String(env), "Role": pulumi.String(value)},
              })
              if err != nil {
                  return err
              }
          }

          ssmParams := map[string]string{"log_level": "INFO", "region": "us-east-1", "max_conns": "100"}
          for key, value := range ssmParams {
              _, err := ssm.NewParameter(ctx, "config-"+key, &ssm.ParameterArgs{
                  Name:  pulumi.Sprintf("/%s/app/%s", env, key),
                  Type:  pulumi.String("String"),
                  Value: pulumi.String(value),
              })
              if err != nil {
                  return err
              }
          }

          ctx.Export("env", pulumi.String(env))
          return nil
      })
  }
  ```
</CodeGroup>

***

## Common mistakes

<Warning>
  ubx v1 does not support nested field access on `each.value` — patterns like `each.value.port` are not yet supported. Use flat string maps where each value is a primitive. If you need structured per-entry configs, use separate `unit` blocks with `when` conditions, or a component.
</Warning>

<Tip>
  Object keys become part of the Pulumi resource name in state. Keep them lowercase, hyphen-safe strings. Avoid keys that look like resource addresses (`aws_s3_bucket`) — keep them short and descriptive (`assets`, `logs`).
</Tip>

***

## Run it

```bash theme={"theme":"css-variables"}
ubx validate
ubx plan             # 3 buckets + 3 SSM parameters
ubx plan --var env=prod
```

***

## What you learned

<Check>`for_each` over an object provides `each.key` (the map key) and `each.value` (the map value)</Check>
<Check>Pulumi keys resources by `each.key` — removing an entry destroys only that resource</Check>
<Check>Flat string maps are the most portable pattern in ubx v1</Check>

***

## Next steps

<CardGroup cols={2}>
  <Card title="count meta-argument" href="/v1/tutorials/08-count-meta-argument">
    Create N copies of a resource by number
  </Card>

  <Card title="when conditional resource" href="/v1/tutorials/09-when-conditional-resource">
    Conditionally create or skip a resource entirely
  </Card>
</CardGroup>

***

<Info>
  Full runnable example: [github.com/ubiquex/ubx-examples/07-for-each-object](https://github.com/ubiquex/ubx-examples/tree/main/07-for-each-object)
</Info>
