Skip to content

Expressions

Zorb uses ${{ }} as a template syntax for injecting dynamic values into workflow configuration. Expressions are resolved before a step executes.

Where expressions work

Expressions are resolved in env: values at every scope (workflow, task, and step level). They also apply to with: inputs on uses: steps.

yml
tasks:
  deploy:
    inputs:
      environment:
        type: string
        required: true
    env:
      TARGET: ${{ inputs.environment }} # ✓ resolved before the step runs
    steps:
      - run: echo "Deploying to $TARGET" # ✓ $TARGET — native shell var

run: strings are never interpolated. They are passed to the shell verbatim. To use an expression result inside a shell command, map it to an env var first and read it natively:

yml
env:
  MODE: ${{ inputs.dry-run ? 'dry-run' : 'apply' }}
steps:
  - run: echo "Running in $MODE mode"    # reads the env var, not an expression

This avoids two layers of substitution and keeps shell steps readable as plain scripts.

Variables

inputs.<name>

Refers to a task input resolved from --with flags or its declared default.

yml
tasks:
  deploy:
    inputs:
      environment:
        type: string
        required: true
      dry-run:
        type: boolean
        default: false
    env:
      TARGET: ${{ inputs.environment }}
      DRY: ${{ inputs.dry-run }}

Input names can contain hyphens (inputs.dry-run). Referencing an input that doesn't exist is an error.

env.<name>

Refers to an environment variable in scope at the point of evaluation. The scope builds up in layers — process environment, then workflow-level env:, then task-level env: — so earlier layers are visible to later ones.

yml
env:
  BASE_URL: https://example.com

tasks:
  ping:
    env:
      HEALTH_URL: ${{ env.BASE_URL }}/health # BASE_URL is in scope here
    steps:
      - run: curl $HEALTH_URL

secrets.<name>

Refers to a value registered into the run-scoped secret table by a previous action (typically a @zorb/secrets/* loader). Secrets are resolved in with: and env: the same as other variables, and any registered value is replaced with *** in step stdout/stderr.

yml
secrets:
  - uses: '@zorb/secrets/load-1password'
    with:
      vault: Production
      items: [DATABASE_URL]

tasks:
  deploy:
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
    steps:
      - run: ./bin/migrate

Referencing a secret that hasn't been registered is an error.

steps.<id>.outputs.<key>

Refers to an output produced by an earlier step in the same task. Steps opt in to outputs by giving themselves an id:.

yml
tasks:
  release:
    steps:
      - id: version
        uses: ./scripts/version.action
      - name: Tag
        env:
          TAG: ${{ steps.version.outputs.tag }}
        run: git tag "$TAG"

Code actions produce outputs from the object returned by action. Shell steps write key=value lines to the file path in $ZORB_OUTPUT. Multi-line values use heredoc syntax:

sh
echo "tag=v1.2.3" >> "$ZORB_OUTPUT"
{
  echo 'notes<<EOF'
  cat CHANGELOG.md
  echo 'EOF'
} >> "$ZORB_OUTPUT"

Referencing an unknown step id or output key is an error.

Operators

Equality: ==, !=

Both sides are coerced to strings before comparison, so true == 'true' and 3 == '3' both hold.

yml
MODE: "${{ inputs.environment == 'prod' ? 'production' : 'staging' }}"

Logical: &&, ||, !

Short-circuit evaluation, same semantics as JavaScript: && returns the first falsy value or the last value; || returns the first truthy value or the last value; ! always returns a boolean.

yml
env:
  SKIP: ${{ inputs.dry-run || inputs.no-deploy }}
  RUN: ${{ !inputs.dry-run }}

Falsy values: false, 0, empty string ''. Everything else is truthy.

Ternary

${{ condition ? value_if_true : value_if_false }}
yml
env:
  TAG: "${{ inputs.env == 'prod' ? 'latest' : inputs.env }}"
  MODE: "${{ inputs.dry-run ? 'dry-run' : 'apply' }}"

The condition can be any expression. Both branches are valid expressions too, including nested ternaries (though deeply nested ternaries are hard to read — prefer mapping inputs to env vars and using shell conditionals instead).

Functions

Call a function directly or chain it as a filter with | (see Filter syntax below).

FunctionSignatureDescription
upperupper(s)Uppercase string
lowerlower(s)Lowercase string
trimtrim(s)Strip leading and trailing whitespace
replacereplace(s, from, to)Replace all occurrences of from with to
containscontains(s, needle)true if s contains needle
startsWithstartsWith(s, prefix)true if s starts with prefix
endsWithendsWith(s, suffix)true if s ends with suffix
lengthlength(s)Character count of s
stringstring(v)Convert any value to its string representation
numbernumber(v)Parse a string to a number; errors if not numeric
booleanboolean(v)Convert to boolean; accepts true/false, 1/0, yes/no
defaultdefault(v, fallback)Return v if non-empty, otherwise fallback
yml
env:
  UPPER_ENV: ${{ upper(inputs.environment) }}
  SAFE_NAME: ${{ replace(inputs.name, '/', '-') }}
  HAS_PREFIX: ${{ startsWith(inputs.tag, 'v') }}
  COUNT: ${{ number(inputs.replicas) }}

Filter syntax

Functions can be applied as filters using |. The value on the left becomes the first argument:

${{ value | fn }}           →  fn(value)
${{ value | fn(arg) }}      →  fn(value, arg)

Filters compose left-to-right:

${{ value | trim | lower }}    →  lower(trim(value))

This is particularly readable for transformation chains:

yml
env:
  TAG: ${{ inputs.version | trim | lower | replace('.', '-') }}
  NAME: ${{ inputs.name | default('anonymous') | upper }}

Error behaviour

Referencing an undefined variable is always an error — there is no silent empty-string fallback. This catches typos early.

yml
env:
  TARGET: ${{ inputs.environemnt }} # error: undefined variable: inputs.environemnt

Calling an unknown function, referencing an undefined secret or step output, or naming a namespace other than inputs, env, secrets, or steps is also an error.

Quoting in YAML

YAML parses : and { as structure characters in certain positions. Wrap any ${{ }} value that might trigger this in double quotes:

yml
env:
  OK: ${{ inputs.env }} # fine — no special chars
  SAFE: "${{ inputs.env == 'prod' ? 'a' : 'b' }}" # quote when : appears

MIT licensed