Building Complex Workflows

Master multi-step workflows with conditions, loops, parallel execution, error handling, and data transformation.

Workflow anatomy

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 in depth

Triggers define when your workflow runs. Okinawa supports several trigger types, each suited to different use cases:

  • Webhook — Runs when an HTTP request is received at your workflow's unique URL. Supports GET, POST, PUT, DELETE methods. Ideal for receiving events from external services.
  • Schedule — Runs on a cron schedule. Use standard cron syntax (0 */6 * * * runs every 6 hours) or human-friendly shortcuts (@hourly, @daily, @weekly).
  • Event — Runs when a connected integration emits an event. For example, trigger on new GitHub pull requests, Slack reactions, or Salesforce opportunity updates.
  • Manual — Runs when triggered from the dashboard or CLI. Useful for on-demand processes like data migrations or one-time reports.
  • Compound — Combine multiple triggers so the workflow runs from any of them. Each trigger provides its own context that steps can access.

Steps and conditions

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).

Switch blocks for multi-branch logic

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"

Error handling and retries

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).

Parallel execution

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.

Loops and iteration

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.

Data transformation

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 slug

Variables and state

Use 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.

Continue Learing