Skip to main content

Expressions

Expressions enable dynamic values in workflow files. They allow you to reference workflow inputs, secrets, and environment variables throughout your workflow configuration.

Syntax

Expressions use a fixed ${{ .context.field }} form - this is not Go template syntax. Most contexts are matched as two dot-separated segments:

${{ .context.field }}

The .steps and .deps contexts are the exception: they use a four-segment form to address a named output:

${{ .steps.<step_id>.outputs.<output_name> }}
${{ .deps.<job_id>.outputs.<output_name> }}

Syntax rules:

  • Fixed segment shapes. Each context has a fixed shape. .inputs, .secrets, .env, and .matrix take exactly one field segment (${{ .context.field }}); .steps and .deps take the four-segment output form. Any other shape is a parse error - for example ${{ .inputs.model.type }} (an extra segment on a two-segment context) and a bare ${{ .inputs }} are both invalid.
  • Identifier charset. Each segment may contain only [A-Za-z0-9_] (letters, digits, and underscore). Names containing other characters cannot be referenced — for example an environment variable named MY-VAR cannot be read with ${{ .env.MY-VAR }} because - is not a valid identifier character. Prefer underscores in env/secret-reference names you intend to reference in expressions.
  • Leading dot required. The context segment must start with a dot (.inputs, .env, …). Omitting it is invalid.
  • Surrounding whitespace inside the braces is optional: ${{.inputs.x}} and ${{ .inputs.x }} are equivalent.

Available Contexts

.inputs - Workflow Input References

Access workflow inputs defined at the workflow level.

Format: ${{ .inputs.<input_name> }}

The <input_name> is the key from the workflow's inputs definition.

Example:

inputs:
model_type:
type: string
required: true
epochs:
type: string
default: "100"

jobs:
train:
env:
MODEL_TYPE: "${{ .inputs.model_type }}"
EPOCHS: "${{ .inputs.epochs }}"

See Workflow Inputs for detailed documentation.

.secrets - Secret References

Access secrets defined at the workflow level.

Format: ${{ .secrets.<reference_name> }}

The <reference_name> is the as field from the secret definition.

Example:

secrets:
- name: workspaces/b9c6e0da-355c-4683-bfbb-b7bf876e7b6b/secrets/ayiffo22n6gu # Resource path in H2O Secure Store
as: registry_token # Internal reference name

jobs:
deploy:
env:
REGISTRY_TOKEN: "${{ .secrets.registry_token }}" # Use the reference name

.env - Environment Variable References

Access environment variables defined at workflow, job, or parent step levels.

Format: ${{ .env.VARIABLE_NAME }}

Example:

env:
DATA_API: https://data.example.com
EXPERIMENT: baseline-v1

jobs:
train:
env:
DATASET_URL: "${{ .env.DATA_API }}/datasets"
RUN_NAME: "Training ${{ .env.EXPERIMENT }}"

Single-level resolution only: When one environment variable references another via ${{ .env.X }}, all .env references are resolved in a single pass against the unresolved values. This means a one-hop reference works (BA where A is a literal), but a chain does not. Given:

env:
A: literal
B: "${{ .env.A }}" # resolves to "literal"
C: "${{ .env.B }}" # resolves to the literal text "${{ .env.A }}", NOT "literal"

C ends up holding the unexpanded text ${{ .env.A }} rather than literal. Avoid chaining env → env → env; reference the original value directly instead.

.matrix - Matrix Variable References

Access matrix variables defined at the job level.

Format: ${{ .matrix.<variable_name> }}

Availability: Only available within jobs that define a matrix field.

Example:

jobs:
train:
matrix:
algorithm: [xgboost, lightgbm, random_forest]
max_depth: ["5", "10", "15"]
steps:
- name: Train model
env:
ALGORITHM: ${{ .matrix.algorithm }}
MAX_DEPTH: ${{ .matrix.max_depth }}
run: python train.py --algorithm $ALGORITHM --max-depth $MAX_DEPTH

See Matrix Jobs for detailed documentation.

.steps - Step Output References

Access the outputs emitted by an earlier step in the same job (or composite action).

Format: ${{ .steps.<step_id>.outputs.<output_name> }}

  • <step_id> is the id of a previous step (see Steps).
  • <output_name> is a key the step wrote to $H2O_WORKFLOWS_OUTPUT (see Step outputs).

Availability: only steps that ran before the referencing step, and only when the producing step defines an id. Because step outputs are produced at run time, .steps is not available in fields evaluated before steps run, such as job/step names and concurrency groups.

Example:

steps:
- id: build
run: echo "image=myrepo/app:1.2.3" >> "$H2O_WORKFLOWS_OUTPUT"

- name: Deploy
env:
IMAGE: ${{ .steps.build.outputs.image }}
run: deploy --image "$IMAGE"

When a step runs a composite action, ${{ .steps.<id>.outputs.<name> }} resolves to the action's declared outputs. See Actions.

.deps - Dependency Job Output References

Access the outputs declared by a job this job depends on.

Format: ${{ .deps.<job_id>.outputs.<output_name> }}

  • <job_id> must be listed in this job's depends_on.
  • <output_name> must be an output that job declares.

Availability: only within a dependent job's steps. An output that was not produced resolves to an empty string. Referencing the outputs of a matrix dependency is not supported.

Example:

jobs:
build:
outputs:
image: ${{ .steps.compile.outputs.image }}
steps:
- id: compile
run: echo "image=myrepo/app:1.2.3" >> "$H2O_WORKFLOWS_OUTPUT"

deploy:
depends_on: [build]
steps:
- run: deploy --image "${{ .deps.build.outputs.image }}"

Where Expressions Work

Expressions can be used in all string fields throughout the workflow, for example:

  • Environment variable values (env).
  • Shell commands (run).
  • Upload/download paths (upload.path, upload.destination, download.source, download.path).
  • Working directories (working_dir).
  • Concurrency group identifiers (concurrency.group) (.inputs and .env only — .secrets references are rejected at validation time because group values are persisted and queried).
  • Job/step names (.inputs, .env, and .matrix only — .secrets references are rejected at validation time because display names are stored and surfaced in API responses).
  • Workflow call inputs.

Type Coercion

When inputs are referenced in expressions, they are automatically converted to strings. This allows inputs of different types to be used anywhere string values are expected.

String Inputs

String inputs are used as-is without conversion:

inputs:
model_type:
type: string
default: "xgboost"

env:
MODEL: ${{ .inputs.model_type }} # Result: "xgboost"

Boolean Inputs

Boolean values are converted to lowercase string literals "true" or "false":

inputs:
debug_mode:
type: bool
default: false

env:
DEBUG: ${{ .inputs.debug_mode }} # Result: "false"

steps:
- run: |
if [ "$DEBUG" = "true" ]; then
echo "Debug mode enabled"
fi

Integer Inputs

Integer values are converted to decimal string representation:

inputs:
max_retries:
type: int
default: 3

env:
RETRIES: ${{ .inputs.max_retries }} # Result: "3"

steps:
- run: python script.py --retries $RETRIES
note

All expression values are strings. The coercion happens automatically when inputs are referenced in expressions.

Limitations

Literal Expression Syntax

There is no escape mechanism for literal ${{ strings. If you need to output the literal text ${{ .inputs.foo }}, it will be evaluated as an expression.

Workaround: Use shell string concatenation or alternative formatting:

run: echo 'Use $''{{ .inputs.name }} to reference inputs'

Security Considerations

Command Injection Risk

User-provided inputs in expressions can contain malicious shell commands (e.g., xgboost; rm -rf /).

Best Practices:

  1. Use environment variables instead of direct interpolation in shell commands:
    steps:
    - name: Train model
    env:
    MODEL_TYPE: "${{ .inputs.model_type }}"
    run: python train.py --model "$MODEL_TYPE"
  2. Validate and sanitize inputs before they reach workflows.
  3. Quote shell variables to prevent word splitting.
  4. Avoid expressions in sensitive fields like file paths when using untrusted inputs.

Feedback