Flow Designer Error Handling: Try/Catch, Fault Handlers, and Retry Logic

A flow without error handling is a flow that will silently fail the moment something unexpected happens — a REST call times out, a record is not found, an external service returns a 500. In production, these failures go unnoticed until a user raises a ticket or an SLA is breached. This guide covers every error handling tool in Flow Designer: Try/Catch steps, fault handlers, the error output variables, retry logic patterns, Flow Error Records, and how to build flows that fail gracefully and noisily.

Why flows fail silently without error handling

By default, when a step in a Flow Designer flow throws an error, the flow stops and marks the execution as Error. No notification is sent. No record is updated. The user who triggered the flow sees nothing — the button they clicked appears to have worked, but nothing happened on the backend.

Error records are created in the Flow Error Records table, but unless someone actively monitors that table, failures go unnoticed. In production environments where flows handle critical business processes — provisioning, approvals, integrations, notifications — silent failures are unacceptable.

The solution is explicit error handling that intercepts failures, logs meaningful diagnostic data, notifies the right people, and ideally recovers automatically from transient errors.

The Try/Catch step

The Try/Catch step is the primary error handling mechanism in Flow Designer. It wraps a block of steps — the Try section. If any step inside the Try throws an error, execution jumps immediately to the Catch section, skipping any remaining steps in the Try.

Basic structure

Try
  ├── Step: REST Action — Call External API
  ├── Step: Script — Process API response
  └── Step: Update Record — Save processed data
Catch (Error)
  ├── Step: Create Record — Log error to audit table
  ├── Step: Send Email — Notify integration team
  └── Step: Update Record — Set status to "Integration Failed"

What triggers the Catch block

The Catch block fires when:

  • A REST Action returns an HTTP 4xx or 5xx status code (depending on the action's error handling configuration)
  • A Script step throws an uncaught JavaScript exception
  • A Look Up Records step returns no results and is configured to throw on no match
  • An IntegrationHub spoke action fails — authentication error, connection refused, timeout
  • A Create Record or Update Record step fails due to ACL restrictions or validation errors
  • A subflow called from within the Try throws an unhandled error

Error data pills in the Catch block

Inside the Catch block, Flow Designer provides error data pills you can use to log or display the error:

  • Error > Message — the human-readable error message from the failed step
  • Error > Stack Trace — the full stack trace (useful for script step failures)
  • Error > Fault Message — the fault message from IntegrationHub spoke failures
  • Error > Step Name — the name of the step that failed
// In a Script step inside the Catch block:
// Use error data pills to build a useful log message
var logMessage = 'Flow failed at step: ' + fd_data.error.step_name 
    + '\nError: ' + fd_data.error.message
    + '\nRecord: ' + fd_data.trigger.incident_record.number;
outputs.log_message = logMessage;

Nested Try/Catch — multiple error zones

You can nest Try/Catch blocks to handle different types of errors differently within the same flow:

// Outer Try — catch any unhandled errors
Try (outer)
  ├── Step: Look Up User record
  │
  ├── Try (inner — REST call error handling)
  │   ├── Step: REST Action — Create Jira ticket
  │   └── Step: Update Record with Jira ticket ID
  │   Catch (inner)
  │   ├── Step: Log REST failure
  │   └── Step: Set flow variable "jira_failed" = true
  │
  └── Step: Condition — if jira_failed = true
      └── Send notification about Jira failure
      
Catch (outer)
  └── Step: Log unexpected failure, notify admin

The inner Catch handles the REST failure specifically — it logs it and sets a variable, but allows the flow to continue. The outer Catch handles anything unexpected that escaped the inner error handling.

The flow-level fault handler

In addition to Try/Catch steps, Flow Designer supports a global fault handler at the flow level. This is a separate section that executes if any unhandled error occurs anywhere in the flow — including errors that escape all Try/Catch blocks.

Add a fault handler in Flow Designer: click the flow properties (three-dot menu on the flow name) and select Add Error Handler. This adds an Error Handler section to the flow that acts as a safety net.

// Flow-level Error Handler
Step: Create Incident — "Automation failure: [Flow Name]"
  Short description: Flow Designer automation failed
  Description: Error data pill > Message
  Assignment group: Automation Team
  Priority: 2

Step: Send Email — Notify automation team
  To: automation-team@company.com
  Subject: FLOW FAILURE: [Flow Name] — [Trigger Record Number]
  Body: Error message + Stack trace data pills

The flow-level fault handler should always create a visible work item (incident, case, or task) so the failure appears in someone's queue. Sending an email is secondary — emails get missed; a P2 incident assigned to the automation team does not.

Retry logic for transient failures

Many flow failures are transient — a network blip, a rate limit hit, a momentarily overloaded external service. These failures will succeed if retried after a short wait. Build retry logic in the Catch block using a flow variable as a counter:

Pattern 1 — Simple retry with wait

// Flow Variables:
// retry_count (Integer) — initial value: 0
// max_retries (Integer) — initial value: 3

Try
  └── Step: REST Action — Call External API
Catch
  ├── Step: Set Flow Variable — retry_count = retry_count + 1
  ├── Condition: retry_count <= max_retries
  │   ├── True:
  │   │   ├── Step: Wait — 30 seconds
  │   │   └── Step: Run Subflow — [same operation as Try, extracted to subflow]
  │   └── False:
  │       ├── Step: Create Incident — Permanent API failure
  │       └── Step: Send notification — admin escalation

Pattern 2 — Exponential backoff

For rate-limited APIs, exponential backoff (doubling the wait time on each retry) is more effective than a fixed interval:

// Wait times: attempt 1 = 10s, attempt 2 = 20s, attempt 3 = 40s

Catch
  ├── Step: Set Variable — retry_count = retry_count + 1
  ├── Step: Script — Calculate wait time
      // Script:
      var waitSeconds = Math.pow(2, retry_count - 1) * 10;
      outputs.wait_seconds = waitSeconds;
  ├── Step: Wait — [wait_seconds data pill] seconds
  └── [retry logic continues]

Error handling for Look Up Records steps

Look Up Records steps have their own error handling configuration. By default, if no records are found, the flow continues with an empty result — it does not throw an error. This can cause null pointer errors on subsequent steps that try to use the empty result.

Configure the Look Up Records step to throw on no results when a missing record is genuinely an error condition:

Look Up Records: Incident
Table: incident
Filter: Number = [trigger variable]
Failure handling: Throw error if no records found

// Then wrap in a Try/Catch:
Try
  └── Look Up Records (configured to throw on no result)
Catch
  └── Handle "record not found" case explicitly

Alternatively, check the result count after the lookup:

Step: Look Up Records — Incident (default — no throw on empty)
Step: Condition — Count of Incident Records > 0
  ├── True: Continue with record data
  └── False: Handle missing record case

Script step error handling

Script steps in flows should use try/catch internally to handle errors that should be caught at the script level rather than propagating to the flow's Catch block:

// Script step with internal error handling
try {
    var parsedData = JSON.parse(fd_data.api_response_body);
    outputs.customer_id = parsedData.customerId;
    outputs.parse_success = true;
} catch (e) {
    // JSON parse failed — set a safe default instead of crashing the flow
    gs.error('Failed to parse API response: ' + e.message, 'OrderProcessingFlow');
    outputs.customer_id = '';
    outputs.parse_success = false;
    // Don't throw — let the flow handle the parse_success = false case
}

The decision whether to catch inside the script or let the error propagate to the flow's Catch block depends on whether the error is recoverable at the script level. Parse failures usually are; API authentication failures are not.

For server-side debugging of script steps, see the debugging guide.

Flow Error Records — investigating failures in production

Every time a flow fails without being caught by error handling, ServiceNow creates a Flow Error Record. Access them at Flow Designer > Flow Error Records (or navigate to sys_flow_context_error.list).

Each error record contains:

  • The flow name and flow instance sys_id
  • The step that failed
  • The error message and stack trace
  • The trigger record (so you can find the record that caused the failure)
  • The execution context — you can replay the execution to see the step-by-step data

Set up a Scheduled Job or a separate flow to monitor the Flow Error Records table and create incidents for failures in critical flows. Proactive monitoring beats reactive incident response.

Execution Details — the flow debugger

Every flow execution creates an Execution Details record showing every step, every input, every output, and the status of each step. Access it from the Flow Executions list (Flow Designer > Flow Executions) or by clicking the execution link in a Flow Error Record.

Execution Details is the most powerful debugging tool for Flow Designer. Unlike the system log where you have to search for relevant entries, Execution Details shows you exactly what happened in a specific execution — what data pills were resolved to what values, which branch was taken, where the error occurred.

Testing error paths

Error paths are the hardest to test manually because they require triggering failure conditions deliberately. A Catch block that was never tested is a Catch block that may not work when you need it.

Testing strategies:

  • Point at a bad endpoint — temporarily change a REST action's endpoint URL to a non-existent one to trigger a connection failure
  • Use a Script step to throw — add a temporary script step in the Try block that throws an exception: throw new Error('Test error');
  • Use an invalid sys_id — pass a non-existent sys_id to a Look Up Records step configured to throw on no result
  • Use ATF for repeatable error testing — build Automated Test Framework tests for your error paths so they can be re-run automatically after changes

Remove or disable test error triggers before deploying to production.

The minimum error handling standard

Every production flow that calls an external system, creates or updates records, or sends notifications should have at minimum:

  1. A Try/Catch around every external API call
  2. A meaningful log record in the Catch block (enough to diagnose what failed and what record was being processed)
  3. A notification to an operations team or a work item in their queue
  4. A status update on the triggering record — "Integration Failed", "Pending Manual Review" — so the user knows something went wrong
  5. A flow-level fault handler as a final safety net for unexpected errors

Related guides for complete Flow Designer development:

The complete Flow Designer reference

The NowSpectrum Flow Designer Playbook — 29 pages covering triggers, actions, subflows, error handling patterns, and 10 production-ready flow examples.

Get the Flow Designer Playbook →
← Back to all posts