Skip to content

Sub-Workflows

A sub_workflow step runs another workflow as a child of the current run and waits for it to finish. Use it when a sequence of steps is shared by several workflows (a standard approval chain, a mailbox provisioning routine) and you would otherwise copy those steps into every parent.

Authoring a sub-workflow step

  1. Drag Sub-Workflow from the Integrations group of the step palette onto the canvas, or drop it directly on a connector to splice it into an existing edge.
  2. Open the step's Configuration tab and pick the Child workflow from the searchable dropdown. The list shows every workflow you can read; the workflow you are currently editing is excluded (see Self-reference).
  3. Fill in the mappings described below.

The step has a single success outgoing edge plus the usual error edge, so it behaves like any other non-branching step in the graph.

Input mappings

Once a child is selected, the panel renders one row per variable the child workflow declares. Each row asks for the parent-side value that feeds it:

Child variable Parent value you supply
email requestor.email
department targetUser.department

The parent-side value is a variable name or a dotted path into the parent run's variables. Leave a row blank to pass nothing for that child variable — the child then falls back to its own default, or fails at start if the variable is required.

Because the rows come from the child's declared variables, you cannot map a value into an input the child does not have. If you add a variable to the child workflow later, reopen the parent step to see the new row.

Output mappings

Outputs flow the other way and are author-added, because a child run can produce variables from its steps that it never declared up front:

Parent variable to write Child value to read
mailboxId createdId
approvalOutcome approval_1.decision

Each row writes one parent variable when the child run completes successfully. If the child fails or is cancelled, the engine returns before the output-mapping loop, so parent variables are left unchanged. A row with an empty parent name is invalid and blocks save — finish or delete it.

Three names are rejected as keys in either mapping: __proto__, constructor, and prototype. Both input and output mappings become property writes (childVars[…] / parent variables[…]), so those names are refused at every layer rather than allowed to reach a write.

Self-reference and recursion

A sub-workflow step cannot point at the workflow that contains it. The designer omits the current workflow from the picker, and save, publish, dry-run validation, and configuration import all reject the combination independently — so a hand-edited export cannot smuggle one in.

That rule only catches the single-hop case. Workflow A calling B calling A is invisible to any single workflow's validator, so the engine enforces a runtime ceiling as well: a sub_workflow step refuses to start a child run once the chain of parent runs is already 10 deep. The step fails with Sub-workflow nesting limit of 10 reached and routes through the step's error edge if one is declared.

Ten levels is a source constant (WorkflowEngine.MAX_SUB_WORKFLOW_DEPTH), not an environment setting. If you hit it, the workflow almost certainly has a cycle rather than a legitimately deep hierarchy.

Runtime behavior

  • The child run is created with parent_run_id set to the parent run, which is what the depth walk follows.
  • The parent step stays in progress until the child run reaches a terminal state; a failed child fails the parent step.
  • A child that a caller can no longer read still runs — authorization is evaluated when the author selects the workflow, not on every execution.
  • A stored config the validator would reject today (from a legacy row or an import that predates these rules) fails the step at start rather than being partially applied.
  • Fork/Join Branches — for running steps in parallel inside one workflow, rather than delegating to another workflow.
  • Versioning & Deprecation — a parent references a workflow by id, so publishing a new version of the child does not repoint existing parents.