Expressions (CEL)
Look up the syntax, operators, and functions available inside a Shipfox expression.
Shipfox expressions use CEL. A workflow writes them in two forms.
A template puts a value into text with ${{ }}. A predicate gives a true
or false result that controls a trigger, job, step, or gate.
For the data an expression can read, see Contexts.
Syntax
Write an expression between ${{ and }}:
run: echo "Building ${{ trigger.event }}"| Form | Example | Result |
|---|---|---|
| Property access | event.ref | The ref property of event |
| Index access | vars["API_URL"] | A property whose name is not an identifier |
| List index | execution.events[0].data | The first element of a list |
| Escape | $${{ | The literal text ${{ |
A predicate field takes the expression on its own. An if field takes exactly
one ${{ }} and nothing else:
if: ${{ trigger.event == "push" }}vars and secrets accept a literal key only. vars["API_URL"] is valid and a
computed key such as vars[inputs.name] is not.
Run identifiers
The run context has a UUID id and a sequential number. The UUID identifies
the run in API and client routes. The number is scoped to the workflow
definition and is available for labels, predicates, and display text. It is not
resolved as a route address.
execution_name: 'Deploy #${{ run.number }}'Types
| Type | Written as |
|---|---|
string | "refs/heads/main" |
int | 42 |
double | 1.5 |
bool | true |
list | ["main", "release"] |
map | {"env": "prod"} |
timestamp | timestamp("2026-01-01T00:00:00Z") |
Operators
| Operator | Example | Result |
|---|---|---|
==, != | trigger.event == "push" | Compares two values of the same type |
<, <=, >, >= | run.created_at > timestamp("2026-01-01T00:00:00Z") | Orders numbers, strings, and timestamps |
&&, ||, ! | job.key == "deploy" && !step.is_retry | Combines boolean values |
in | "prod" in inputs.environments | True when a list contains the value |
in | "API_URL" in vars | True when a map contains the key |
+ | "v" + inputs.version | Adds numbers, joins strings, joins lists |
-, *, /, % | executions.size() % 2 == 0 | Arithmetic on numbers |
? : | run.number == 1 ? "first" : "rerun" | The middle value when true, the last when false |
( ) | (a || b) && c | Groups an expression against precedence |
Comparing values of different types is an error rather than a false result, and
int and double count as different types. A predicate that errors never
passes.
Functions and macros
| Call | Example | Result |
|---|---|---|
list.size(), string.size() | executions.size() > 1 | Number of elements or characters |
list.all(item, predicate) | executions.all(e, e.status == "succeeded") | True when every element matches |
list.exists(item, predicate) | execution.events.exists(e, e.event == "push") | True when at least one element matches |
list.exists_one(item, predicate) | needs.exists_one(j, j.status == "failed") | True when exactly one element matches |
list.filter(item, predicate) | executions.filter(e, e.status == "failed") | The elements that match |
list.map(item, expression) | needs.map(j, j.key) | The expression applied to each element |
string.contains(part) | event.ref.contains("release") | True when the string contains part |
string.startsWith(part), string.endsWith(part) | event.ref.startsWith("refs/heads/") | Prefix and suffix tests |
string.matches(pattern) | event.ref.matches("^refs/tags/v[0-9]+$") | True when the string matches a regular expression |
string.split(separator) | event.ref.split("/")[2] | The parts between each separator |
string.lowerAscii(), string.upperAscii() | event.ref.lowerAscii() | The string in one case |
has(field) | has(event.pull_request) | True when the field is present |
string(value), int(value), double(value) | "run-" + string(run.number) | The value converted to that type |
timestamp(string) | timestamp("2026-01-01T00:00:00Z") | A timestamp from an RFC 3339 string |
range(start, stop, step) | range(1, 3, 1) | An inclusive list of integers |
toJson(value) | toJson(event.pull_request.labels) | The value serialized as compact JSON text |
fromJson(string) | fromJson(event.payload) | The JSON text parsed into a CEL value |
range() accepts a positive step and can materialize at most 1,000 values and
1,000,000 context-byte fan-out units in one evaluation. toJson() can produce
at most 1,000,000 UTF-8 bytes in one evaluation. fromJson() expects a valid
JSON string and converts safe integer numbers to CEL integers. toJson() writes
integers outside the range JSON numbers represent exactly as quoted strings.
The CEL language definition documents the full standard library.
Related pages
Contexts
Every context, its properties, and the fields that can read it.
Workflow schema
The fields that carry these expressions.
Context and templating
Why templates and predicates are separate, and how commands receive values.