Master multi-step workflows with conditions, loops, parallel execution, error handling, and data transformation.
Every Okinawa workflow consists of three core components: triggers, steps, and outputs. Understanding how these fit together is the key to building powerful automations that handle real-world complexity. In this guide, we'll go beyond the basics and explore conditions, loops, error handling, parallel execution, and advanced data transformation.
By the end, you'll be able to build workflows that rival custom microservices — without writing a single line of backend code.
Triggers define when your workflow runs. Okinawa supports several trigger types, each suited to different use cases:
0 */6 * * * runs every 6 hours) or human-friendly shortcuts (@hourly, @daily, @weekly).Steps are the building blocks of your workflow. Each step performs a single action — calling an integration API, transforming data, or making a decision. Steps execute sequentially by default, but you can control flow with conditions:
steps:\n - name: Check Priority\n type: builtin\n action: condition\n if: "{{trigger.body.priority}} == 'high'"\n then:\n - name: Page On-Call\n type: pagerduty\n action: createIncident\n title: "{{trigger.body.title}}"\n else:\n - name: Send to Slack\n type: slack\n action: sendMessage\n channel: "#low-priority-alerts"\n message: "Low priority: {{trigger.body.title}}"
Conditions support comparison operators (==, !=, >, <, >=, <=), logical operators (and, or, not), and string operations (contains, startsWith, endsWith).
When you need more than two branches, use switch blocks:
steps:\n - name: Route by Department\n type: builtin\n action: switch\n field: "{{trigger.body.department}}"\n cases:\n engineering:\n - name: Notify Engineering Slack\n type: slack\n action: sendMessage\n channel: "#eng-alerts"\n marketing:\n - name: Notify Marketing Slack\n type: slack\n action: sendMessage\n channel: "#marketing-alerts"\n default:\n - name: Notify General\n type: slack\n action: sendMessage\n channel: "#general-alerts"
Production workflows need robust error handling. Okinawa provides several mechanisms:
steps:\n - name: Call External API\n type: http\n action: post\n url: "https://api.example.com/process"\n retry:\n max_attempts: 3\n backoff: exponential\n initial_delay: 1000\n on_error:\n - name: Send Failure Alert\n type: slack\n action: sendMessage\n channel: "#workflow-errors"\n message: "Step failed after 3 retries: {{step.error.message}}"
The retry block supports three strategies: fixed (constant delay), exponential (doubling delay), and linear (increasing by fixed amount). You can also set a per-step timeout with timeout: 30000 (30 seconds).
Use the parallel block to run multiple steps concurrently. This is essential when steps don't depend on each other and you want to minimize total execution time:
steps:\n - name: Process in Parallel\n type: builtin\n action: parallel\n steps:\n - name: Update CRM\n type: salesforce\n action: upsertContact\n email: "{{trigger.body.email}}"\n - name: Add to Mailing List\n type: hubspot\n action: addContact\n email: "{{trigger.body.email}}"\n - name: Send Welcome Email\n type: sendgrid\n action: sendEmail\n to: "{{trigger.body.email}}"\n template: "welcome-v2"
All three steps start simultaneously. The workflow waits for all of them to complete before continuing. If any step fails, you can configure whether the entire parallel block fails or continues with fail_fast: true/false.
Iterate over arrays and collections using the foreach block. Each iteration runs in its own context with access to the current item:
steps:\n - name: Process Each Item\n type: builtin\n action: foreach\n items: "{{trigger.body.orders}}"\n step:\n name: Sync Order\n type: salesforce\n action: createOrder\n amount: "{{item.total}}"\n customer: "{{item.customer_name}}"\n concurrency: 5\n batch_size: 10
The concurrency option limits how many iterations run at once, preventing rate limit issues. The batch_size option groups items for APIs that support bulk operations.
Between steps, you often need to transform data. Okinawa includes a powerful expression language:
{{step.1.output.body | json_path: '$.user.email'}} — Extract a nested field{{trigger.body.tags | join: ', '}} — Join an array into a string{{trigger.body.created_at | date: 'YYYY-MM-DD'}} — Format a date{{trigger.body.amount | math: 'multiply: 100' | round: 2}} — Numeric operations{{trigger.body.name | slugify}} — Convert to URL-safe slugUse variables to store data across steps and maintain state between workflow runs:
variables:\n processed_count: 0\n last_run: null\n\nsteps:\n - name: Update Counter\n type: builtin\n action: setVariable\n key: processed_count\n value: "{{vars.processed_count | math: 'add: 1'}}"\n\n - name: Update Timestamp\n type: builtin\n action: setVariable\n key: last_run\n value: "{{now}}"
Variables persist across workflow runs and are scoped to the workflow. Use them for deduplication, rate limiting, and tracking progress.