Skip to main content

Variables and Environments

Variables

$ variable_name = value
$ variable_name = $(command)
$ variable_name = env("NAME")
$ variable_name = secret.env("NAME")
$ variable_name = secret.file("./path/to/value.txt")
$ variable_name = [[ prompt_name ]]
$ variable_name = [[ prompt_name = default_value ]]
$ variable_name = [foo, bar]
  • Scalars are the default building block for reusable values.
  • $(...) runs a shell command at preparation time.
  • Prompt placeholders can declare defaults.
  • Arrays expand a request once per value.

Prompt defaults are plain text up to the closing ]], so URL-shaped values such as [[ ws_origin = wss://example.com ]] are valid.

Array Expansion

$ USER = [alice,bob]

---

Fetch profile

GET https://api.example.com/users/{{ USER }}

^ & status == 200

Hen turns that single request block into mapped requests, one per array value. In this example, the planner creates Fetch profile [USER=alice] and Fetch profile [USER=bob].

When a request references two array variables, Hen creates mapped requests for the Cartesian product of those values.

Arrays are intentionally constrained:

  • values must stay simple scalars
  • no nested arrays
  • no whitespace inside array items
  • up to two array variables per request and 128 total combinations

Generated requests are suffixed with the chosen values, such as [USER=foo].

If a mapped request fails, Hen aborts the remaining iterations. Exports from each iteration are suffixed with the same selected-value label.

Mapped requests may depend on the matching expanded iteration of another mapped request when they share compatible iteration bindings. Unmapped requests still cannot depend on a mapped request, and ambiguous mapped matches still fail. If multiple downstream requests need the same unmapped setup, move that setup into an unmapped helper request.

Named Environments

$ API_ORIGIN = https://api.example.com
$ CLIENT_ID = [[ client_id ]]

env local
$ API_ORIGIN = http://localhost:3000
$ CLIENT_ID = hen-local

env staging
$ API_ORIGIN = https://staging.example.com
  • Environment blocks are valid only in the collection preamble.
  • Environment overrides may only target previously declared scalar variables.
  • Environment blocks may contain scalar overrides plus dotenv PATH directives.
  • Other nested syntax such as arrays and shell substitutions is rejected inside environment blocks.

Dotenv Overlays

dotenv .env

$ API_ORIGIN = env("API_ORIGIN")

env local
dotenv .env.local

[ PROFILE == "local" ] dotenv .env.local
  • dotenv PATH is valid only in the collection preamble.
  • Top-level dotenv directives apply to every run.
  • The selected named environment can add more dotenv directives inside its env ... block.
  • Dotenv directives may use the existing bracketed guard prefix.
  • Paths are plain string-like values in this slice. Interpolation such as dotenv {{ path }} or dotenv [[ path ]] is rejected.
  • hen verify validates dotenv syntax and guards, and reports duplicate-path or missing-file warnings from resolved paths, without reading dotenv contents.
  • During runs, env("NAME") and secret.env("NAME") both check the real process environment first and then fall back to loaded dotenv values. Later dotenv directives override earlier ones.
  • Missing dotenv files are skipped with a warning instead of failing the run.
  • Dotenv directives do not create Hen variables automatically. Bind non-secret keys with env(...) and secret keys with secret.env(...).

Selection Order

Resolution order is:

  1. Collection preamble scalar assignments
  2. Selected named environment
  3. Explicit CLI --input key=value values
  4. Prompt defaults declared with [[ name = default ]]
  5. Runtime captures and callback exports

Run-Time Selection

Use:

hen run ./collection.hen 0 --env local --input client_id=demo --non-interactive

If no environment is selected, Hen uses the collection defaults.

For secret-backed values, continue to Secrets and redaction.