Portal Community

When to Use

Configuration

FieldRequiredDefaultDescription
status Required The final execution status to record. Must be one of "success", "cancelled", or "failed". This value is written to ExecutionStatusID in execution history and determines how a parent SubWorkflow node routes after this child workflow terminates.
message Optional A static human-readable reason for stopping. Recorded in execution history as stop_message. Visible in the BizFirst monitoring dashboard and execution detail views. If both message and message_expression are set, message_expression takes precedence.
message_expression Optional A BizFirst expression that evaluates to the stop message at runtime. Takes precedence over the static message field when both are present. Use this to include dynamic context — error details, entity IDs, or processing summaries — in the recorded message.
 message_expression priority: If both message and message_expression are configured, the expression is evaluated first. If the expression resolves successfully, its result is used as the stop message. The static message value is only used if message_expression is absent or evaluates to null.

Status Semantics

StatusSubWorkflow parent routes toWhen to Use
success parent's success port The workflow completed its intended purpose. All required steps executed without issue. Also correct for early exits where the intended outcome was already achieved (e.g. record already exists, no action needed).
cancelled parent's error port (CHILD_WORKFLOW_CANCELLED) The workflow was intentionally stopped due to a business condition — not an error. Use for expected non-completions: approval rejected, product unavailable, customer already processed, user-initiated cancellation. The workflow did not fail; there was simply no useful work to complete.
failed parent's error port (CHILD_WORKFLOW_FAILED) An unrecoverable condition was encountered and the workflow could not complete its purpose. Use for validation failures after retries, external API errors that cannot be resolved, data integrity violations, or any condition that requires human attention or escalation.

Sample Configuration

Explicit success with a summary message

{
  "status": "success",
  "message_expression": "{{ 'Invoice ' + $output.createInvoice.invoiceId + ' generated and emailed to ' + $json.customerEmail }}"
}

Business rule cancellation (static message)

{
  "status": "cancelled",
  "message": "Customer already has an active subscription. No renewal action required."
}

Failed with dynamic exception detail

{
  "status": "failed",
  "message_expression": "{{ 'Validation failed: ' + $var.__exception_message__ }}"
}

Sub-workflow result signal (validation child)

{
  "status": "failed",
  "message_expression": "{{ 'Invalid customer record: ' + $output.validateFields.errors }}"
}

Validation Errors

StopWorkflow performs minimal validation — it is designed to always be reachable. There are no pre-execution configuration errors. The only runtime condition that prevents execution is if status is missing entirely from the node configuration, which will default to recording an empty status string.

ConditionBehaviour
status field is missing or emptyWorkflow terminates but ExecutionStatusID is recorded as empty string. Monitoring dashboards may show an unknown status. Always provide a status value.
message_expression evaluation failsThe expression error is silently swallowed and stop_message falls back to the static message value if present, or records an empty message. The workflow still terminates with the configured status.

Output / Execution Ports

StopWorkflow has no output ports. It is a terminal node — no downstream connections are possible. When the workflow reaches a StopWorkflow node, execution ends for the current workflow context.

Execution History Fields Written

FieldTypeDescription
ExecutionStatusIDstringThe resolved status value: "success", "cancelled", or "failed". Written to the workflow execution record and used by parent SubWorkflow nodes for port routing.
stop_messagestringThe resolved message (from message_expression if set, otherwise message). Stored in execution history and displayed in the monitoring dashboard execution detail view.
stopped_atstring (ISO-8601)UTC timestamp of when StopWorkflow executed and the workflow terminated.
stopped_by_nodestringThe node key of this StopWorkflow node — identifies which node in the graph triggered termination, useful when a workflow has multiple StopWorkflow nodes on different branches.

Sample Output

Execution history record written by StopWorkflow

{
  "ExecutionStatusID": "failed",
  "stop_message": "Validation failed: email field is not a valid email address",
  "stopped_at": "2026-05-26T11:42:08Z",
  "stopped_by_node": "stopOnValidationError"
}

Parent SubWorkflow error port — child stopped with "failed"

// $output.runValidation on parent's error port:
{
  "error": {
    "code": "CHILD_WORKFLOW_FAILED",
    "message": "Validation failed: email field is not a valid email address"
  }
}

Expression Reference

StopWorkflow is a terminal node and produces no output data. It writes only to execution history. The following expressions are available in the parent workflow on the error port after a child workflow terminates via StopWorkflow.

ExpressionResult (available in parent workflow)
{{ $output.runValidation.error.message }}The stop_message from the child's StopWorkflow node, surfaced on the parent's error port when status was "cancelled" or "failed".
{{ $output.runValidation.error.code }}Either CHILD_WORKFLOW_CANCELLED or CHILD_WORKFLOW_FAILED depending on the child's stop status.
 Parallel branch termination: If a workflow is executing parallel branches (via ParallelFork) and one branch reaches a StopWorkflow node, all other in-progress parallel branches are terminated immediately. The workflow does not wait for other branches to complete before applying the terminal status. Design parallel workflows so that StopWorkflow nodes are placed only after a ParallelJoin, not inside individual parallel branches, unless early termination of the entire workflow is intended.

Node Policies & GuardRails

Policy AreaRecommendation
Explicit terminal nodes Always use StopWorkflow at the end of all main execution paths. Workflows that end by running out of connected nodes do not write an explicit terminal status — they complete with an ambiguous state. StopWorkflow ensures every execution path produces a clear, auditable outcome in history.
Descriptive messages Always provide a message or message_expression. Stop messages appear in execution history and are the primary diagnostic tool when reviewing failed or cancelled workflow runs. A message like "Validation failed: email is invalid" is immediately actionable; an empty message requires re-running the workflow with debug logging to understand what happened.
Consistent status semantics across sub-workflows Within a suite of related sub-workflows, establish a clear convention: "success" = intended outcome achieved, "cancelled" = graceful non-completion (expected business condition), "failed" = unexpected error requiring action. Inconsistent usage makes parent error port handling ambiguous and complicates monitoring dashboards.
Parent error port handling When a sub-workflow uses StopWorkflow with "cancelled" or "failed", the parent SubWorkflow node routes to its error port. Always connect the parent's error port — leaving it unconnected causes the parent branch to terminate silently with no record of the child's stop reason.
cancelled vs failed distinction Use "cancelled" for expected non-completions where the workflow made an intentional decision not to proceed — rejection by a business rule, user-initiated stop, item already processed. Use "failed" only for conditions that are unexpected, represent an error state, or require human investigation or alerting. Conflating the two pollutes failure metrics with expected business outcomes.
Placement after ParallelJoin In workflows using ParallelFork/ParallelJoin, place StopWorkflow nodes only after the ParallelJoin node, not inside individual parallel branches. A StopWorkflow inside a parallel branch terminates all other branches immediately — if that is not the intended behaviour, use a VariableAssignment to record the outcome and let the ParallelJoin aggregate it instead.

Examples

Example 1 — Validation Sub-Workflow Signaling Parent

A "Validate Order" child workflow checks product availability, customer credit, and shipping region eligibility. It uses StopWorkflow to signal the parent with a precise status and message. The parent SubWorkflow node routes to success or error accordingly, keeping all validation logic encapsulated in the child.

// Inside child workflow "Validate Order" (workflow ID: 55)
ManualTrigger  [key: "start"]
  └─► HttpRequest  [key: "checkInventory"]
        GET /inventory/{{ $json.productId }}
        └─► IfCondition  [key: "hasStock"]
              Condition: {{ $output.checkInventory.quantityAvailable > 0 }}
              ├─► true ──► HttpRequest  [key: "checkCredit"]
              │              GET /customers/{{ $json.customerId }}/credit
              │              └─► IfCondition
              │                    Condition: {{ $output.checkCredit.approved == true }}
              │                    ├─► true  ──► StopWorkflow  (status: "success", message: "Order validation passed")
              │                    └─► false ──► StopWorkflow  (status: "failed", message: "Credit check failed for customer {{ $json.customerId }}")
              └─► false ──► StopWorkflow  (status: "cancelled", message: "Product {{ $json.productId }} is out of stock")

// Inside parent workflow:
SubWorkflow  [key: "validateOrder"]
  sub_workflow_id: 55
  ├─► success ──► [proceed to fulfillment]
  └─► error   ──► Slack  (alert ops: {{ $output.validateOrder.error.message }})

Example 2 — CatchBlock Error Termination with Dynamic Message

A TryBlock wraps an external payment API call. If the call fails after retries, the CatchBlock catches the exception and routes to StopWorkflow with a message_expression that embeds the actual error detail from the exception variable into the recorded message.

TryBlock  [key: "tryPayment"]
  └─► body ──► HttpRequest  [key: "chargeCard"]
                POST /payments/charge
                body: { cardToken: {{ $json.cardToken }}, amount: {{ $json.amount }} }
  └─► catch ──► IfCondition  [key: "isRetryable"]
                  Condition: {{ $output.chargeCard.statusCode == 429 || $output.chargeCard.statusCode == 503 }}
                  ├─► true  ──► Delay  (duration_seconds: 30)
                  │              └─► success ──► [retry TryBlock — up to 3 attempts via Loop]
                  └─► false ──► StopWorkflow
                                  status: "failed"
                                  message_expression: "{{ 'Payment failed (non-retryable): HTTP ' + $output.chargeCard.statusCode + ' — ' + $var.__exception_message__ }}"

Example 3 — Multi-Branch Workflow with Explicit Success and Cancellation Exits

An order fulfilment workflow branches by order type. Each branch terminates with an explicit StopWorkflow node carrying a context-specific message. A shared "already fulfilled" early-exit path uses "cancelled" to distinguish intentional skipping from an actual error.

WebhookTrigger  [key: "orderReceived"]
  └─► HttpRequest  [key: "checkDuplicate"]
        GET /orders/{{ $json.orderId }}/status
        └─► IfCondition  [key: "isDuplicate"]
              Condition: {{ $output.checkDuplicate.alreadyFulfilled == true }}
              ├─► true ──► StopWorkflow
              │              status: "cancelled"
              │              message_expression: "{{ 'Order ' + $json.orderId + ' already fulfilled on ' + $output.checkDuplicate.fulfilledAt + '. Skipped.' }}"
              └─► false ──► Switch  [key: "routeByType"]
                              ├─► "digital" ──► HttpRequest  (POST /delivery/digital  ...)
                              │                  └─► StopWorkflow
                              │                        status: "success"
                              │                        message_expression: "{{ 'Digital delivery sent for order ' + $json.orderId }}"
                              └─► "physical" ──► HttpRequest  (POST /warehouse/pick  ...)
                                                 └─► StopWorkflow
                                                       status: "success"
                                                       message_expression: "{{ 'Warehouse pick initiated for order ' + $json.orderId + ', tracking: ' + $output.warehousePick.trackingId }}"