---
title: "Flow fault paths, done properly"
url: "https://trailassembly.com/flow-fault-paths/"
topic: "Flow and Automation"
published: "2026-09-10T04:39:03+00:00"
updated: "2026-10-06T13:51:48+00:00"
---

# Flow fault paths, done properly

**The answer, briefly:** put a fault path on every element that touches the database, and make each one end somewhere a human or a record will actually see. A fault path routed to a bare End element is the worst of your options — it converts a loud, fully rolled-back failure into a silent, half-committed one, and you find out months later from a data quality complaint.

Salesforce Code Analyzer’s [MissingFaultHandler](https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/rules-flow.html) rule flags missing fault paths on Get, Create, Update, and Delete elements at high severity — the right default. What it cannot tell you is whether the fault path you added is worth having, which is the harder half of the job.

## What happens when you do nothing

The default is ugly and safe. For a record-triggered flow, the failing element throws, the flow stops, and the whole transaction rolls back — including the user’s original save. They get an error naming the flow, and Salesforce emails the failure to whoever last modified it, or to the Apex exception email recipients if you changed that setting.

In a [screen flow](https://trailassembly.com/screen-flow-validation/) the user sees “An unhandled fault has occurred in this flow” and cannot continue. Useless to them, but the data is consistent and somebody gets told.

So the baseline is bad UX and good integrity. Every fault path trades some of that integrity for control, and your job is to hand back something better.

## What adding a fault path actually changes

Two things, and the second surprises people. The flow no longer counts as failed, so the default error email stops arriving — if you want it, you now send it yourself.

And the automatic rollback no longer happens on your behalf. Records the flow wrote before the failure stay pending and commit with the rest of the transaction. That is why the Roll Back Records element exists: on a fault path it discards the flow’s pending changes, so you are not left with a child record whose parent update never landed.

| Handling | Data state after a failure | Who finds out |
| --- | --- | --- |
| No fault path | Consistent — the transaction rolls back | The user, plus an email to whoever last edited the flow |
| Fault path that logs, then rolls back | Consistent | Anyone who reports on the log object |
| Fault path to a bare End element | Whatever the flow wrote before failing is committed | Nobody |

## Where fault paths genuinely earn their place

### Screen flows and anything a person is watching

Route the fault to a screen that says what failed in plain language and offers the one action the user can take, rolling back anything the flow already wrote first. This is the one case where a fault path improves the user’s day rather than the admin’s.

### Failures caused by conditions outside your flow

Row lock contention, a callout timing out, an action depending on a downstream system. These fail intermittently for reasons the flow cannot fix, so “record it and move on” is real error handling rather than error hiding.

### Scheduled paths, asynchronous paths, and batch runs

Nobody is watching a screen, and the default email goes to whoever last edited the flow. Usually the wrong person, usually filtered. A fault path that writes a log record is the only telemetry you will have here, so it is the difference between knowing and guessing.

### When you have a real alternative behaviour

Inside a loop, skipping the failed record and continuing is often correct. As long as that record is written down somewhere. So is falling back to a default owner when the intended one is inactive. Both are decisions rather than suppressions.

## Where a fault path makes things worse

Salesforce’s own error-handling examples document the bypass pattern: connect the fault path to the same element as the normal path and the flow carries on as if nothing happened. It is how orgs end up with thousands of records missing a field that “the flow sets.”

The test is blunt. If your fault path does not leave the data consistent *and* record the failure somewhere a person will look, it is strictly worse than no fault path. The default at least rolls back and sends mail. Delete it and let the flow fail.

Be most suspicious of the fault path added “for now, until we build the logging.” That temporary state has a way of becoming the architecture.

## A logging pattern that holds up

Build one custom object, `Flow_Error_Log__c` is a fine name, with fields for the flow API name, the element name, the fault message as a long text area, the affected record Id, the [running user](https://trailassembly.com/dashboard-running-user/), and the timestamp. Every fault path then does two things: create a log record, and either continue safely or roll back.

Two details separate a log you trust from one you don’t:

- **Write the element name as literal text**, by hand, in the Create Records element. The fault message often does not identify which element produced it, and a log saying only “an error occurred” costs you the investigation you were trying to avoid.

- **If you also roll back, the log record rolls back with it.** Same transaction, same fate. To keep both, publish a platform event configured to publish immediately (that delivery survives a rolled-back transaction), and let a subscriber flow write the log row in its own transaction. The cost is two more pieces of metadata plus event allocations, so use it where losing the log matters.

For notification, send to a distribution alias or a queue’s members, never a named individual. Salesforce documents the “email me on every fault” approach and it works, but a custom fault email beats the default only if its subject line names the record and the element. Otherwise you have rebuilt the thing everyone ignores.

## Using $Flow.FaultMessage properly

`{!$Flow.FaultMessage}` holds the platform’s error text and is available only inside a fault path. Put it in the log field verbatim, without interpreting it. The raw text is what lets you match a failure to a known error class later.

Two limits. It can be generic for some action and [subflow](https://trailassembly.com/flow-subflow-variables/) failures, which is the argument for logging the element name and inputs alongside it. And it is written for admins, naming API fields and error codes, so keep it off end-user screens: show them a sentence you wrote, log the fault message where you will see it.

And do not use fault paths for “this should not be allowed.” That is the Custom Error element’s job: it blocks the save, rolls back, and shows a message you control on the field you nominate.

## Confirming the fault path works

Fault paths are the least-tested part of most flows, because making one fail on purpose takes effort. Three reliable ways: point Update Records at a hardcoded [18-character](https://trailassembly.com/invalid-id-15-vs-18/) Id that does not exist, blank a field the target object requires, or trip a [validation rule](https://trailassembly.com/validation-rule-vs-flow/) you switch on for the test.

Then do it at volume. Load 200 records where you know one will fail: you want a log row for that record, the other 199 processed, and no partial writes attached to the bad one. If all 200 vanish, your fault path did not do what you think it did.

## Frequently asked

### Does a fault path stop the flow error email?

Yes. Once the fault is handled, the flow has not failed, so no default email is sent. Send your own from the fault path if you still want to be told.

### Can I put a fault path on a before-save flow?

There is nothing to attach it to. Before-save flows have no DML elements, so the way to protect them is defensive logic. [check for nulls](https://trailassembly.com/flow-null-dereference/) and for empty Get Records results before you use them.

### Will a fault path let the rest of my loop finish?

Only if you connect it back into the loop deliberately. Route the fault to your logging element, then back to the loop’s next-iteration connector, and be sure the skipped record ends up in the log.

### Where do I see failures if I do not build logging?

Setup → Paused and Failed Flow Interviews, plus the error emails. Both are adequate for one flow and hopeless for fifty, which is the point at which the log object pays for itself.

---

*References: [Flow Scanner Rules Reference](https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/guide/rules-flow.html), Salesforce Code Analyzer. Plus the Salesforce Help documents “Customize What Happens When a Flow Fails”, “What Happens When a Flow Fails?”, “Other Examples of Error Handling in Flows”, “Configure Every Fault Path to Send You an Email”, and “Custom Error Element”.*

---

Source: https://trailassembly.com/flow-fault-paths/
Trail Assembly — independent Salesforce write-ups.
