Command-line interface for running and interacting with the Durable Workflow Server.
Three options depending on what you have installed:
1. Standalone binary (no PHP required). The easiest path — a one-liner installer that detects your OS and arch:
# Linux and macOS
curl -fsSL https://durable-workflow.com/install.sh | sh# Windows
irm https://durable-workflow.com/install.ps1 | iexOr download a native binary directly from the releases
page. Available assets:
dw-linux-x86_64, dw-linux-aarch64,
dw-macos-aarch64, dw-windows-x86_64.exe.
macOS x86_64 standalone binaries are not currently produced because the
macos-13 runner label is not available to this org; Intel Mac users can
run the PHAR with a system PHP.
2. PHAR (requires PHP >= 8.2). Download dw.phar from the
releases page and run it
with php dw.phar (or chmod +x and call directly — the PHAR
has a #!/usr/bin/env php shebang).
3. Composer.
composer global require durable-workflow/climake phar # Build the PHAR (requires PHP >= 8.2 and Composer)
make binary # Build the PHAR plus a standalone native binary for the
# current platform (downloads Box and static-php-cli on demand)
make clean # Remove build artifactsBuild artifacts land in ./build/. See scripts/build.sh
for the underlying steps; tools are cached under build/.tools/.
Set the server URL and auth token via environment variables:
export DURABLE_WORKFLOW_SERVER_URL=http://localhost:8080
export DURABLE_WORKFLOW_AUTH_TOKEN=your-token
export DURABLE_WORKFLOW_NAMESPACE=defaultOr pass them as options to any command:
dw --server=http://localhost:8080 --token=your-token --namespace=production workflow:listThe CLI targets control-plane contract version 2 automatically via
X-Durable-Workflow-Control-Plane-Version: 2 and expects canonical v2
response fields such as *_name and wait_for. Non-canonical legacy aliases
such as signal and wait_policy are rejected.
The server also emits a nested control_plane.contract document with schema
durable-workflow.v2.control-plane-response.contract, version 1, and
legacy_field_policy: reject_non_canonical. The CLI validates that nested
boundary before trusting the server-emitted legacy_fields,
required_fields, and success_fields metadata.
For request fields such as workflow:start --duplicate-policy and
workflow:update --wait, the CLI now reads the server-published
control_plane.request_contract manifest from GET /api/cluster/info before
sending the command. Supported servers publish schema
durable-workflow.v2.control-plane-request.contract, version 1, with an
operations map. The CLI treats missing or unknown request-contract
schema/version metadata as a compatibility error instead of silently guessing.
Use dw server:info to inspect the current canonical values,
rejected aliases, and removed fields advertised by the target server.
CLI version 0.1.x requires Server 2.x (versions 2.0.0+).
The CLI automatically validates server version on first invocation and raises a clear error if incompatible:
$ dw workflow:list
Server version 3.0.0 is incompatible with dw CLI 0.1.x (requires server 2.x).
Upgrade the server or use a compatible CLI version.See the Version Compatibility documentation for the full compatibility matrix across all components.
# Check server health
dw server:health
# Show server version and capabilities
dw server:info
# Start a local development server
dw server:start-dev
dw server:start-dev --port=9090 --db=sqlite# Start a workflow
dw workflow:start --type=order.process --input='{"order_id":123}'
dw workflow:start --type=order.process --workflow-id=order-123
dw workflow:start --type=order.process --execution-timeout=3600 --run-timeout=600
# List workflows
dw workflow:list
dw workflow:list --status=running
dw workflow:list --type=order.process
# Describe a workflow
dw workflow:describe order-123
dw workflow:describe order-123 --run-id=01HXYZ --json
# Send a signal
dw workflow:signal order-123 payment-received --input='{"amount":99.99}'
# Query workflow state
dw workflow:query order-123 current-status
# Send an update
dw workflow:update order-123 approve --input='{"approver":"admin"}'
# Cancel a workflow (workflow code can observe and clean up)
dw workflow:cancel order-123 --reason="Customer request"
# Terminate a workflow (immediate, no cleanup)
dw workflow:terminate order-123 --reason="Stuck workflow"
# View event history
dw workflow:history order-123 01HXYZ
dw workflow:history order-123 01HXYZ --follow# List namespaces
dw namespace:list
# Create a namespace
dw namespace:create staging --description="Staging environment" --retention=7
# Describe a namespace
dw namespace:describe staging
# Update a namespace
dw namespace:update staging --retention=14# Create a schedule
dw schedule:create --workflow-type=reports.daily --cron="0 9 * * *"
dw schedule:create --schedule-id=daily-report --workflow-type=reports.daily --cron="0 9 * * *" --timezone=America/New_York
# List schedules
dw schedule:list
# Describe a schedule
dw schedule:describe daily-report
# Pause/resume
dw schedule:pause daily-report --note="Holiday freeze"
dw schedule:resume daily-report
# Trigger immediately
dw schedule:trigger daily-report
# Backfill missed runs
dw schedule:backfill daily-report --start-time=2024-01-01T00:00:00Z --end-time=2024-01-07T00:00:00Z
# Delete a schedule
dw schedule:delete daily-report# List task queues
dw task-queue:list
# Describe a task queue (pollers, backlog)
dw task-queue:describe default# Complete an activity externally
dw activity:complete TASK_ID --result='{"status":"done"}'
# Fail an activity externally
dw activity:fail TASK_ID --message="External service unavailable" --non-retryable# Show task repair diagnostics
dw system:repair-status
# Run a task repair sweep
dw system:repair-pass
# Show expired activity timeout diagnostics
dw system:activity-timeout-status
# Run activity timeout enforcement sweep
dw system:activity-timeout-pass
# Target specific execution IDs
dw system:activity-timeout-pass --execution-id=EXEC_ID_1 --execution-id=EXEC_ID_2| Option | Description |
|---|---|
--server, -s |
Server URL (default: $DURABLE_WORKFLOW_SERVER_URL or http://localhost:8080) |
--namespace |
Target namespace (default: $DURABLE_WORKFLOW_NAMESPACE or default) |
--token |
Auth token (default: $DURABLE_WORKFLOW_AUTH_TOKEN) |
The CLI uses a stable exit-code policy so scripts and CI pipelines can react
to specific failure modes without parsing stderr. Values follow Symfony
Console's canonical 0/1/2 for success / failure / usage, and extend
from there:
| Code | Name | Meaning |
|---|---|---|
| 0 | SUCCESS |
Operation completed successfully. |
| 1 | FAILURE |
Generic failure — command ran but did not succeed. |
| 2 | INVALID |
Invalid usage — bad arguments, unknown options, or local validation. Also returned for HTTP 4xx responses that are not covered below (e.g. 400, 422). |
| 3 | NETWORK |
Could not reach the server (connection refused, DNS failure, TLS handshake failure, transport error). |
| 4 | AUTH |
Authentication or authorization failure. Returned for HTTP 401 and 403. |
| 5 | NOT_FOUND |
Resource not found. Returned for HTTP 404. |
| 6 | SERVER |
Server error. Returned for HTTP 5xx. |
| 7 | TIMEOUT |
Request timed out before the server responded. Also returned for HTTP 408. |
Example:
dw workflow:describe chk-does-not-exist
echo $? # 5 (NOT_FOUND)
dw server:health --server=http://unreachable:9999
echo $? # 3 (NETWORK)Exit codes are defined in DurableWorkflow\Cli\Support\ExitCode
and are covered by tests/Commands/ExitCodePolicyTest.php.
Every list, describe, read, and mutating command supports --json for
machine-readable output. Mutating commands (POST/PUT/DELETE) return the
server's raw response body when --json is set, making them safe to pipe
into jq or feed into downstream tooling.
# Read surface — stable even when no --json flag is passed for list views.
dw workflow:list --json | jq '.workflows[].workflow_id'
# Mutating surface — capture server response for idempotent automation.
wf_id=$(dw workflow:start --type=orders.Checkout --json | jq -r '.workflow_id')
dw workflow:signal "$wf_id" approve --json | jq '.command_status'MIT