DOC_INDEX
THEME
DOCS/Learn OrchStep/Composing Tasks

Composing Tasks

Tasks calling tasks - parameterized, reusable building blocks within one workflow

Steps can call tasks (task:), which makes tasks your unit of reuse inside a workflow: define deploy_to_env once, call it three times with different parameters.

Calling a task with parameters

name: promotion

defaults:
  app_name: "checkout-service"

tasks:
  deploy_to_env:
    desc: "Deploy to one environment (parameterized)"
    vars:
      env_name: ""
      replicas: "1"
    steps:
      - name: deploy
        func: shell
        do: echo "deploying {{ vars.app_name }} to {{ vars.env_name }} ({{ vars.replicas }} replicas)"

  main:
    steps:
      - name: dev
        task: deploy_to_env
        vars:
          env_name: "dev"
          replicas: "1"
      - name: staging
        task: deploy_to_env
        vars:
          env_name: "staging"
          replicas: "4"

The calling step's vars: become the called task's parameters - same precedence rules as everywhere else.

Transform: data shaping between tasks

When data needs reshaping between steps, transform runs JavaScript with access to vars and steps:

- name: aggregate
  func: transform
  do: |
    const regions = [
      Number(steps.fetch_apac.orders),
      Number(steps.fetch_emea.orders),
    ];
    return { total: regions.reduce((a, b) => a + b, 0) };

- name: report
  func: shell
  do: 'echo "total orders: {{ steps.aggregate.total }}"'

Two verified rules keep transform predictable:

  1. Transform code is not template-rendered - {{ }} inside it passes through as literal text.
  2. Since v0.8.0, transform sees the same merged vars as templates (including --var). On older engines, stage runtime values through a previous step's outputs (JS always sees steps.*).

Parallel composition

Independent work runs concurrently in a parallel: block; every child's outputs merge into the normal steps.* namespace afterwards (verified):

- name: build_all
  parallel:
    - name: build_frontend
      func: shell
      do: echo "frontend built"
      outputs: { ok: "true" }
    - name: build_backend
      func: shell
      do: echo "backend built"
      outputs: { ok: "true" }

- name: package
  func: shell
  do: echo "fe={{ steps.build_frontend.ok }} be={{ steps.build_backend.ok }}"

If any parallel child fails (and is not caught), the block fails.

Run it

orchstep run

Where to go next

Modules - the same idea across workflow boundaries: reusable, versioned units shared between projects and teams.