Required pairing: CatchBlock must always follow a TryBlock. It fires only when an unhandled exception is thrown inside the TryBlock scope. On the success path (no exception), the CatchBlock is skipped and execution proceeds to FinallyBlock (if present) or continues normally.
When to Use
- Log errors to a database: Insert exception details into an error log table — type, message, stack trace, workflow ID, and the data being processed — for support teams and post-mortem analysis.
- Alert the team via Slack or email: Send an immediate alert when a critical integration fails, including the exception type and message so the on-call engineer can triage without opening the workflow editor.
- Graceful degradation with default values: If an enrichment API fails, set default values in the catch scope and let the main workflow continue — the user or downstream system receives a partial result rather than a workflow error.
- Create error tickets in Jira or Linear: Use an HttpRequest node in the catch scope to call a ticket API, pre-populating the ticket with exception details for the engineering backlog.
- Return a user-friendly error response: In customer-facing API workflows, the catch scope constructs a sanitised, human-readable error message instead of exposing internal exception details.
Configuration
| Field | Required | Description |
Behavior |
Optional |
Controls what happens after the catch scope completes. Valid values: continue (default) — execution continues to FinallyBlock and then the rest of the workflow; stop — execution halts after the catch scope (FinallyBlock still runs); transform — the exception is transformed into a structured output and passed downstream. |
ErrorVariablePrefix |
Optional |
Custom prefix for the three exception variables. Default prefix is empty, producing __exception_type__, __exception_message__, __exception_stacktrace__. Set to e.g. paymentErr for nested try/catch blocks to avoid variable name collisions: paymentErr_type__, paymentErr_message__, etc. |
Behavior Values
| Value | Behaviour After Catch Scope Completes |
continue | The workflow continues normally after the catch scope. Execution proceeds to FinallyBlock (if present) and then to whatever follows the try/catch/finally block. Use when the error was handled and the workflow can safely resume. |
stop | Workflow execution stops after the catch scope. FinallyBlock still runs (for cleanup), but no further workflow nodes execute after FinallyBlock. Use when the error is unrecoverable and the workflow must not continue. |
transform | The caught exception is converted to a structured data output. The catch scope can modify the error data, and execution continues — with the transformed error data available to downstream nodes. Use when you want to convert exceptions into structured error response objects. |
Output Ports
| Port | Fires When |
success | An exception was caught and the catch scope nodes should execute. Error variables are populated. Connect logging, alerting, and recovery nodes here. |
error | The CatchBlock itself encountered an error during its own initialisation (rare — typically a configuration issue). Always wire this port to prevent silent failure of error handling. |
Exception Variables Written to Memory
When CatchBlock fires, the following variables are written into ExecutionMemory. Access them with $var.<name>:
| Variable (default prefix) | Type | Description |
__exception_type__ | string | The .NET exception class name — e.g. HttpRequestException, TimeoutException, KeyNotFoundException. Use with Switch for type-specific handling. |
__exception_message__ | string | Human-readable description of the exception. Include in alert notifications and error log records. |
__exception_stacktrace__ | string | Full .NET stack trace. Valuable for debugging. Include in error log inserts; do not expose to end users. |
With ErrorVariablePrefix: "payErr", the variables become:
| Variable (custom prefix) | Description |
payErr_type__ | Exception type with custom prefix. |
payErr_message__ | Exception message with custom prefix. |
payErr_stacktrace__ | Stack trace with custom prefix. |
OutputData Fields
The CatchBlock's own output data (accessible via $output.<catchNodeKey>):
| Field | Type | Description |
__exception_type__ | string | Exception type (same as the memory variable). |
__exception_message__ | string | Exception message. |
__exception_stacktrace__ | string | Stack trace. |
Sample Output
Error Port — Exception Caught
{
"error": {
"errorCode": "HTTP_REQUEST_FAILED",
"message": "Connection refused: upstream API at https://api.external.com/orders returned 503",
"statusCode": 503,
"nodeName": "fetchOrders",
"nodeType": "HttpRequest",
"timestamp": "2025-03-15T09:45:22.000Z"
},
"context": {
"workflowId": "wf_order_sync",
"executionId": "exec_20250315_001",
"attemptNumber": 1
}
}
Expression Reference
| Expression | Returns |
{{ $var.__exception_type__ }} | Exception class name, e.g. HttpRequestException |
{{ $var.__exception_message__ }} | Error message string |
{{ $var.__exception_stacktrace__ }} | Full stack trace (multi-line string) |
{{ $output.myCatch.__exception_type__ }} | Same, accessed via node output reference |
Validation Errors
| Error | Cause |
VAL_INVALID_BEHAVIOR | Behavior is set to a value other than continue, stop, or transform. |
VAL_CATCH_WITHOUT_TRY | CatchBlock is placed in the workflow without a preceding TryBlock in scope. |
Node Policies and GuardRails
- Never put CatchBlock outside TryBlock scope: CatchBlock is only valid when it follows a TryBlock. Placing it elsewhere produces a validation error and the workflow cannot be published.
- FinallyBlock must follow CatchBlock: If you have a FinallyBlock, it must come after the CatchBlock — not before. Execution order is always TryBlock scope → CatchBlock scope → FinallyBlock scope.
- Use Switch on exception type for smart routing: Connect a Switch node in the catch scope keyed on
{{ $var.__exception_type__ }} to route different exception types to different handlers — retries for timeouts, alerts for auth errors, logging-only for validation errors.
- Use ErrorVariablePrefix for nested try/catch: When one TryBlock is nested inside another, set different prefixes on each CatchBlock to prevent the inner exception variables from overwriting the outer ones.
- Keep catch scope focused: Avoid putting complex business logic in the catch scope. The catch scope should: log the error, notify the team, set fallback values, and optionally trigger a retry. Complex logic belongs in the main workflow path after the try/catch/finally block.
- Don't expose stack traces to end users: Use
__exception_message__ for user-facing messages and reserve __exception_stacktrace__ for internal error logs only.
Pattern Examples
Pattern 1 — Log Error and Continue
If an optional enrichment call fails, log it to the database and set a default value. The main workflow continues normally with the default value.
TryBlock [key: "enrichTry"]
└─► success ──► HttpRequest [key: "enrichApi"]
GET /enrich/{{ $json.customerId }}
└─► VariableAssignment (creditScore = response.score)
CatchBlock [key: "enrichCatch"]
Behavior: continue
└─► success ──► MongoDB (insert into enrichment_errors:
{ type: {{ $var.__exception_type__ }},
msg: {{ $var.__exception_message__ }},
customerId: {{ $json.customerId }} })
└─► VariableAssignment (creditScore = -1) (default)
FinallyBlock [key: "enrichFinally"]
└─► success ──► [use $var.creditScore — either real or default]
Pattern 2 — Type-Based Exception Routing
Route different exception types to different handlers using Switch on the exception type variable.
CatchBlock [key: "apiCatch"]
Behavior: continue
└─► success ──► Switch [key: "routeByError"]
Expression: {{ $var.__exception_type__ }}
Cases: {
"HttpRequestException": "handleNetworkError",
"UnauthorizedException": "handleAuthError",
"TimeoutException": "handleTimeout"
}
DefaultPort: "handleUnknownError"
├─► handleNetworkError ──► Delay (2s) ──► [retry]
├─► handleAuthError ──► EmailSmtp (alert: auth expired)
├─► handleTimeout ──► SlackNode (alert: API slow)
└─► handleUnknownError ──► MongoDB (log full stack trace)
Pattern 3 — Stop on Critical Failure with Notification
For unrecoverable failures (e.g., a core data write), stop the workflow after alerting the team. FinallyBlock still runs to update the status record.
CatchBlock [key: "criticalCatch"]
Behavior: stop
└─► success ──► SlackNode (#critical-alerts)
Message: "CRITICAL: {{ $var.__exception_type__ }} in OrderWrite
{{ $var.__exception_message__ }}
OrderId: {{ $json.orderId }}"
└─► MongoDB (insert into failed_orders:
{ orderId, exceptionType, exceptionMsg, ts })
FinallyBlock [key: "criticalFinally"]
└─► success ──► MongoDB (update orders SET status='failed'
WHERE id={{ $json.orderId }})