Skip to content
Expressions

Expression reference

Orchard uses HCL expressions with a cty type system. This page covers the reference namespaces, type expressions, and available functions.

Reference namespaces

Five reference namespaces are available inside expressions:

FormScopeAvailable when
var.<name>Scenario (or component) variables.Immediately — resolved up front.
action.<type>.<name>.<output>Action outputs.After the referenced action completes.
component.<type>.<name>.<output>Component instance outputs.After the component resolves.
var.<name> (inside a component)The component’s own variables.Immediately, scoped to the component.
self.<output>The current action’s own outputs.Inside lifecycle { teardown { } } blocks only.

Variable references

provider "postgres" {
  dsn = var.dsn
}

action "postgres_query" "insert" {
  query = "INSERT INTO t (name) VALUES ('${var.name}')"
}

Variable references are resolved at plan time. They cannot participate in the dependency graph — if you need ordering, reference an action or component output instead.

Action references

action "postgres_query" "next" {
  query = "SELECT * FROM users WHERE id = ${action.postgres_query.previous.user_id}"
}

output "merchant_id" {
  value = action.postgres_query.create_merchant.id
}

action.<type>.<name>.<output> — all three segments are required. The <output> segment depends on what the provider’s action schema declares.

Component references

action "postgres_query" "attach" {
  query = "INSERT ... VALUES (${component.merchant.acme.id})"
}

component.<type>.<name>.<output> — where <type> matches the component’s declared name in its source file and <name> matches the instance label in the scenario.

self (teardown only)

Inside a lifecycle { teardown { } } block, self.<field> refers to the action’s own recorded outputs. This lets authors write teardowns without repeating the node name:

action "postgres_query" "create_merchant" {
  query = "INSERT INTO merchants (name) VALUES ('${var.name}') RETURNING id"

  lifecycle {
    teardown {
      query = "DELETE FROM merchants WHERE id = ${self.id}"
    }
  }
}

self is only populated when orchard teardown <record> evaluates the body; the values come from the record’s per-node outputs captured at run time. Inside the action’s main body (during orchard run), self is not in scope.

Type expressions

Types are written as cty type expressions:

ExpressionMeaning
stringString.
numberNumber (integer or float).
boolBoolean.
list(T)List of T.
set(T)Set of T.
map(T)Map from string to T.
object({ k = T, ... })Object with fixed keys.
tuple([T1, T2, ...])Tuple with positional types.
anyAny cty value (dynamic type).

Examples:

variable "count" {
  type    = number
  default = 1
}

variable "tags" {
  type    = list(string)
  default = ["demo", "dev"]
}

variable "config" {
  type = object({
    retries = number
    timeout = string
  })
}

Omitting type is equivalent to declaring type = any.

String interpolation

HCL supports ${ ... } interpolation inside strings:

query = "INSERT INTO merchants (name) VALUES ('${var.name}')"

Quote carefully when interpolating into SQL, shell commands, or HTTP URLs — Orchard does not escape values for you.

Operators

Standard HCL operators are available:

  • Arithmetic: +, -, *, /, %
  • Comparison: ==, !=, <, <=, >, >=
  • Logical: &&, ||, !
  • Conditional: cond ? a : b
variable "environment" { default = "dev" }

action "postgres_query" "seed" {
  query = var.environment == "prod" ? "SELECT 1" : "INSERT INTO ..."
}

Functions

See the functions reference for the complete list of all available functions with signatures, descriptions, parameter tables, and examples. The reference is auto-generated from Orchard’s function registry so it stays in sync with the binary.

Random values and seeds

Every random function (uuid, timerandom, timesequence, and the rest of the random and identity functions) draws from the run’s seed. Pass --seed <n> to orchard run or orchard plan, or pin one in the scenario with seed = <n>; the flag wins. Without either, each run picks a random seed, prints it as seed: <n>, and stores it in the run record, so any run can be repeated:

orchard run scenarios/demo.hcl            # seed: 1742…
orchard run scenarios/demo.hcl --seed 1742…  # the same random values again

Each attribute of each action, each variable default, each provider setting and each output draws from its own stream, derived from the seed and that place’s address. So the values one action gets don’t change when you add or remove other actions, or run with --parallelism, and orchard plan --show-values --seed <n> shows the values the run with that seed uses.

A seed fixes the random part only. now(), env(), and the timestamp in time-ordered IDs such as ulid() still change between runs. And two runs with the same seed generate the same uuid() keys, so a second run before tearing down the first can hit unique constraints.

Reference syntax in depends_on

depends_on accepts references as bare traversals — not strings:

action "postgres_query" "b" {
  depends_on = [action.postgres_query.a, component.merchant.acme]
  query      = "..."
}

These references don’t pull any data into scope; they only create graph edges.