Write steps, not plumbing
Define your workflow as a sequence of named tasks in YAML. No SDK, no orchestration scaffolding, no boilerplate. Your workflow file is the entire implementation.
Durability out of the box
Every workflow runs on Temporal. Retries, crash recovery and execution history are handled for you, without any extra configuration.
Catch errors early
Zigflow validates your workflow file before execution starts. Invalid constructs and unsupported fields are rejected with clear, actionable error messages.
See how it works
Two steps. Fetch a user profile, then send a welcome email. If either step fails, Temporal retries it automatically using the retry policy defined in the YAML.
Your workflow is a single file. Validate it, run it and share it.
Try your first workflowdocument:
dsl: 1.0.0
namespace: acme
name: onboard-user
version: 1.0.0
do:
- fetchProfile:
call: http
with:
method: get
endpoint: ${ "https://api.acme.com/users/" + ($input.userId | tostring) }
output:
as:
profile: ${ . }
- sendWelcome:
call: http
metadata:
activityOptions:
retryPolicy:
maximumAttempts: 3
with:
method: post
endpoint: https://api.acme.com/emails
body:
to: ${ .profile.email }
template: welcome
Every Zigflow workflow runs on Temporal, a battle-tested engine for durable execution. You get automatic retries, crash recovery and full execution history without writing SDK code.
Example workflows
A selection of real workflow patterns, each defined in YAML and ready to run. Full examples are also available in the GitHub repo.
- Child Workflows
- Custom Search Attributes
- Debugging
- External Calls
- For
- Heartbeat
- Query Listeners
- Scheduling
- Signal Listeners
- Update Listeners
Child Workflows
Define multiple workflows and call a child workflow from a parent
document:
dsl: 1.0.0
namespace: zigflow
name: childWorkflow
version: 0.0.1
title: Child Workflows
summary: Define multiple workflows and call a child workflow from a parent
timeout:
after:
minutes: 1
do:
- parentWorkflow:
do:
- wait:
wait:
seconds: 5
# Call child workflow as a single workflow - this will be run synchronously
- callChildWorkflow1:
run:
workflow:
name: child-workflow1
namespace: default
version: 0.0.0
# Do a fan-out child workflow - this will be run asynchronously
- fanOut:
fork:
compete: false
branches:
- callWorkflow1:
run:
workflow:
name: child-workflow1
namespace: default
version: 0.0.0
- callWorkflow2:
run:
workflow:
name: child-workflow2
namespace: default
version: 0.0.0
- wait:
output:
as:
completed: true
wait:
seconds: 5
- child-workflow1:
do:
- wait:
wait:
seconds: 10
- child-workflow2:
do:
- wait:
wait:
seconds: 3
Custom Search Attributes
How to add custom search attribute data into your Temporal workflows
document:
dsl: 1.0.0
namespace: zigflow
name: searchAttributes # Workflow name
version: 0.0.1
title: Custom Search Attributes
summary: How to add custom search attribute data into your Temporal workflows
input:
schema:
format: json
document:
type: object
required:
- userId
properties:
userId:
type: number
timeout:
after:
minutes: 1
do:
- wait:
metadata:
searchAttributes:
waitTime:
type: int
value: 5
wait:
seconds: 5
- getUser:
call: http
metadata:
# The search attributes must be configured in your Temporal service - with great power comes great responsibility
searchAttributes:
hello:
type: text
value: world
call:
type: text
value: ${ $data.task.name }
userId:
type: int
value: ${ $input.userId }
with:
method: get
endpoint: ${ "https://jsonplaceholder.typicode.com/users/" + ($input.userId | tostring) }
- wait:
metadata:
searchAttributes:
waitTime:
type: int
value: 3
wait:
seconds: 3
Debugging
An example of how to use CloudEvents for debugging workflows
document:
dsl: 1.0.0
namespace: zigflow
name: cloudevents
version: 0.0.1
title: Debugging
summary: An example of how to use CloudEvents for debugging workflows
# Optionally validate the input received
input:
schema:
format: json
document:
type: object
required:
- userId
properties:
userId:
type: number
timeout:
after:
minutes: 1
do:
- baseData:
export:
as:
baseData: ${ . }
set:
# Set a variable from an envvar
envvar: ${ $env.EXAMPLE_ENVVAR }
uuid: ${ uuid }
object:
hello: world
uuid: ${ uuid }
inputUserId: ${ $input.userId }
now: ${ now }
now_formatted: ${ now | strftime("%Y-%m-%d %H:%M:%S") }
timestamp: ${ timestamp }
timestamp_formatted: ${ timestamp | strftime("%Y-%m-%d %H:%M:%S") }
now_iso8601: ${ timestamp_iso8601 }
array:
- ${ uuid }
- hello: world
- wait:
wait:
seconds: 5
- getUser:
call: http
export:
as: '${ $context + { getUser: . } }'
metadata:
# Configure activity options for this task only
activityOptions:
retryPolicy:
maximumAttempts: 5
with:
method: get
endpoint: ${ "https://jsonplaceholder.typicode.com/users/" + ($input.userId | tostring) }
- raiseAlarm:
# A fork is a series of child workflows running in parallel
output:
as: '${ $context + del(.multiStep) }'
fork:
# If not competing, all tasks will run to the finish - this is the default behaviour
compete: false
branches:
# A single step is passed in by the Serverless Workflow task
- callNurse:
call: http
with:
method: get
endpoint: https://jsonplaceholder.typicode.com/users/2
# Multiple steps can be passed in by the Serverless Workflow do task
- multiStep:
do:
- wait1:
wait:
seconds: 3
- wait2:
wait:
seconds: 2
# Another single step child workflow
- callDoctor:
call: http
with:
method: get
endpoint: ${ "https://jsonplaceholder.typicode.com/users/" + ($input.userId | tostring) }
External Calls
An example of how to use Zigflow to make external gRPC and HTTP calls
document:
dsl: 1.0.0
namespace: zigflow
name: external-calls
version: 0.0.1
title: External Calls
summary: An example of how to use Zigflow to make external gRPC and HTTP calls
do:
- fork:
fork:
compete: false
branches:
- grpc:
call: grpc
with:
proto:
endpoint: file:///go/app/examples/external-calls/grpc/basic/proto/basic/v1/basic.proto
service:
name: providers.v1.BasicService
host: grpc
port: 3000
method: Command1
arguments:
input: ${ $env.GRPC_INPUT }
- http:
call: http
with:
method: get
endpoint: https://jsonplaceholder.typicode.com/users/3
For
How to use the for loop task
document:
dsl: 1.0.0
namespace: zigflow
name: for-loop
version: 0.0.1
title: For
summary: How to use the for loop task
input:
schema:
format: json
document:
type: object
required:
- map
- data
properties:
map:
type: object
required:
- key1
- key2
- key3
properties:
key1:
type: string
key2:
type: number
key3:
type: boolean
data:
type: array
items:
type: object
required:
- userId
properties:
userId:
type: number
do:
# Iterate over the map object
- forTaskMap:
export:
as: '${ $context + { forTaskMap: . } }'
for:
in: ${ $input.map }
do:
- setData:
export:
as: ${ . }
set:
key: "${ \"hello: \" + $data.index }"
value: ${ $data.item }
- wait:
output:
as: ${ $context }
wait:
seconds: 2
# Iterate over the data array
- forTaskArray:
export:
as: '${ $context + { forTaskArray: . } }'
for:
each: item
in: ${ $input.data }
at: index
# while: ${ $data.item.userId != 4 } # If this returns false, it will cut the iteration
do:
# Each iteration will run these tasks in order
- setData:
export:
as: ${ . }
set:
userId: ${ $data.item.userId } # Get the userId for this iteration
id: ${ $data.index } # Get the key
processed: true
- wait:
output:
as: ${ $context }
wait:
seconds: 1
- forTaskNumber:
export:
as: '${ $context + { forTaskNumber: . } }'
for:
in: ${ 5 }
do:
- setData:
export:
as: ${ . }
set:
number: ${ $data.item }
- wait:
output:
as: ${ $context }
wait:
seconds: 1
- forTaskStateCarryOver:
output:
as: '${ $context + { forTaskStateCarryOver: . } }'
for:
in: ${ 5 }
while: '${ ($output.pageNumber // 0) < 4 }'
do:
- incrementPage:
output:
as: '${ { pageNumber: ($context.pageNumber + 1), iteration: $data.index } }'
export:
as: '${ $context + { pageNumber: ($context.pageNumber + 1) } }'
set:
previousPageNumber: ${ $context.pageNumber }
nextPageNumber: ${ $context.pageNumber + 1 }
iteration: ${ $data.index }
Heartbeat
Set [activity heartbeat](https://docs.temporal.io/encyclopedia/detecting-activity-failures#activity-heartbeat). Useful on long-running activities.
document:
dsl: 1.0.0
namespace: zigflow
name: heartbeat
version: 0.0.1
title: Heartbeat
summary: Set [activity heartbeat](https://docs.temporal.io/encyclopedia/detecting-activity-failures#activity-heartbeat). Useful on long-running activities.
do:
- longRunningActivity:
metadata:
# Trigger a heartbeat every 8 seconds - this MUST be less than your heartbeatTimeout
heartbeat:
seconds: 8
activityOptions:
# Set a heartbeat timeout of 10 seconds
heartbeatTimeout:
seconds: 10
run:
shell:
# Sleep for 30s - this will timeout if heartbeat doesn't run
command: sleep
arguments:
- "30"
Query Listeners
Listen for Temporal query events
document:
dsl: 1.0.0
namespace: zigflow
name: query
version: 0.0.1
title: Query Listeners
summary: Listen for Temporal query events
metadata:
# Acts as override to test out continue-as-new
# @link https://docs.temporal.io/develop/go/continue-as-new
canMaxHistoryLength: 5
do:
- queryState:
listen:
to:
one:
with:
# ID maps to the query name in Temporal
id: get_state
# Temporal query - used to make a non-blocking read request
type: query
# This data will be returned as-is
data:
id: ${ $data.id }
progressPercentage: ${ $data.progressPercentage }
status: ${ $data.status }
- createState:
output:
as:
data: ${ . }
set:
# Items created at top-level are persisted
id: ${ uuid }
status: not started
progressPercentage: 0
- wait:
wait:
seconds: 5
- updateState:
set:
progressPercentage: 33
status: running
- wait:
wait:
seconds: 5
- updateState:
set:
progressPercentage: 66
# status remains as "running" if it remains as top-level item
- wait:
wait:
seconds: 5
- stateComplete:
set:
progressPercentage: 100
status: finished
- wait:
wait:
seconds: 5
Scheduling
Schedule the tasks to be triggered automatically
document:
dsl: 1.0.0
namespace: zigflow
name: schedule
version: 0.0.1
title: Scheduling
summary: Schedule the tasks to be triggered automatically
metadata:
# Set the workflow name to trigger - this will either be the document.name or the Do task (see multiple-workflows example)
scheduleWorkflowName: schedule
# Optionally set the schedule ID name
scheduleId: some-schedule
# Optionally set any input for the workflow when triggered - this can receive envvars
scheduleInput:
- msg:
- hello
- world
envvars: ${ $env.EXAMPLE_ENVVAR }
timeout:
after:
minutes: 1
schedule:
# Every is supported as a Temporal interval - https://pkg.go.dev/go.temporal.io/sdk/client#ScheduleIntervalSpec
every:
minutes: 3
# Cron is supported as a Temporal cronjob - timezone is UTC
cron: "0 0 * * *"
do:
- wait:
wait:
seconds: 5
Signal Listeners
Listen for Temporal signal events
document:
dsl: 1.0.0
namespace: zigflow
name: signal
version: 0.0.1
title: Signal Listeners
summary: Listen for Temporal signal events
do:
- approveListener:
metadata:
timeout: 10s # Controls the AwaitWithTimeout timeout - defaults to 60s
listen:
to:
one:
with:
# ID maps to the signal name in Temporal - this blocks until received
id: approve
# Temporal signal - used to make write request
type: signal
acceptIf: ${ $data.approveListener }
- outputSignal:
export:
as: '${ $context + { response: . } }'
set:
# Get the data from the approveListener signal
signal: ${ $data.approveListener }
- wait:
output:
as: ${ $context }
wait:
seconds: 5
Update Listeners
Listen for Temporal update events
document:
dsl: 1.0.0
namespace: zigflow
name: updates
version: 0.0.1
title: Update Listeners
summary: Listen for Temporal update events
do:
- callDoctor:
metadata:
timeout: 10s # Controls the AwaitWithTimeout timeout - defaults to 60s
listen:
to:
# Only progress after every update received
all:
- with:
# ID maps to the update name in Temporal
id: temperature
# Temporal update - used to make read/write request
type: update
acceptIf: ${ $data.temperature > 38 }
- with:
id: bpm
type: update
acceptIf: ${ $data.bpm < 60 or $data.bpm > 100 }
- wait:
output:
as:
temperature: ${ $data.temperature }
bpm: ${ $data.bpm }
wait:
seconds: 10