Logic Composer
Error handling
Catch and recover from failing steps with Try / Catch / Finally, stop a flow on purpose with Raise Error or Halt, and know exactly what can and cannot be caught.
By default, a step that fails ends the whole flow with an error. That is often right: if a required row is missing, carrying on would do more harm. When a failure is expected and you know how to recover, for example an external system that is sometimes down, wrap the risky steps in Try / Catch. When the flow itself detects a problem, stop it on purpose with Raise Error or Halt.
Your tools
Section titled “Your tools”| Tool | Use it to | Does the flow continue? |
|---|---|---|
| Try / Catch | Run steps that may fail, recover in Catch, clean up in Finally | Yes, after the Try / Catch step (unless the error cannot be caught, see below) |
| Raise Error | Stop the flow with your own error code and message | No. The caller receives the code and message. |
| Halt | Stop the flow early, successfully | No, but it ends without an error and returns the outputs set so far |
| Call Rest Api branches | Handle each HTTP status code in its Response branch, and a failed call in Unexpected Error | Yes |
| Condition checks | Test for empty results before using them, for example after Get Entity By Id | Yes |
Not everything that looks like a failure is an error. Get Entity By Id and Execute Query (Single) return an empty result when nothing matches; they do not fail. Test for it with a Condition (for example Not Null(GetCase)) before you use the row.
Try / Catch / Finally
Section titled “Try / Catch / Finally”Add a Try / Catch step from the Control category. It has no settings, only three branches:
| Branch | Runs |
|---|---|
| Try | Always, first. Put the steps that may fail here. |
| Catch | Only when a step in Try fails. The error message is in the step’s Error variable, for example ErpSync.Error. |
| Finally | Always, last: after Try succeeds, after Catch handles an error, and also before an error, Halt or Raise Error ends the flow. |
After Catch runs, the flow continues with the step after Try / Catch. An empty Catch silently ignores the error. Any branch can be empty, so you can add the step now and fill it in later.
The Error variable is Text and is visible only inside Catch. To use the message after the Try / Catch, copy it into a variable you declared before it with Update Variable.
If a step in Catch fails, Finally still runs and the error then ends the Try / Catch step. An outer Try / Catch can catch it; otherwise the flow fails.
What Catch handles
Section titled “What Catch handles”Catch handles an error raised by any step in Try, at any depth:
- a step that fails, for example Save Entity rejected by a business rule or a missing privilege;
- steps nested inside Try, such as inside a Condition or Iterator;
- an error inside a logic block or logic flow that a step in Try calls;
- an expression that fails while it runs, and a Call Rest Api step that cannot send its request (for example when a required parameter has no value).
A REST call that is sent and fails goes to the step’s Unexpected Error branch instead, and Catch does not see it. See Call Rest Api.
What Catch does not handle
Section titled “What Catch does not handle”| Situation | What happens |
|---|---|
| Halt inside Try | The flow ends without an error, as intended. Finally runs first. |
| Raise Error inside Try | The flow ends with your error, as intended. Finally runs first. |
| Break inside Try | The surrounding Loop or Iterator ends after Finally runs. |
| A Dataverse or Dynamics 365 error while the flow runs inside the platform’s transaction | The flow ends with the error. Finally runs first. |
The last row needs care. When a flow runs as part of a record operation, for example when a recipe runs it synchronously as a row is created or updated, the flow runs inside that operation’s transaction. If Dataverse (or Dynamics 365 on-premises) reports an error at that point, the platform has already rolled the whole transaction back, so the flow cannot safely carry on. Catch therefore does not run, Finally still runs, and the error ends the flow and the operation.
This applies the same way to both editions. The on-screen help on the Try / Catch step says so: “Halt and Raise Error are not caught, and neither are Dataverse or Dynamics 365 errors while the flow runs inside the platform’s transaction (for example, in a synchronous plugin step).”
Only errors reported by Dataverse or Dynamics 365 are affected. Errors from other sources inside Try, such as a logic block or expression, are still caught. When a flow must recover from Dataverse errors, for example to log a failed save and carry on, make sure it does not run synchronously inside a record operation: run it in the background (for example a recipe’s After stage with Async on) instead.
Raise Error
Section titled “Raise Error”Raise Error stops the flow with an Error Code (required) and an optional Error Message expression. The flow ends with an application error, and the code and message are returned to whoever started it. The Run dialog, for example, reports the run as Failed with your error code.
What happens to the changes the flow already made depends on where it runs:
-
Inside a record operation (a recipe’s Validation, Before or synchronous After stage, or a synchronous event handler): Raise Error cancels the operation, and Dataverse rolls back every change the flow made together with the operation itself.
-
From the Run dialog or as a Dataverse action: the run is a single Dataverse request, and Raise Error rejects it. Plan for its changes to be rolled back, and test this in your environment before you rely on it.
-
In the background (a schedule, or an asynchronous recipe stage or event handler): there is no surrounding transaction. Changes the flow already saved are kept; Raise Error only ends the flow. In a scheduled flow, the run is still recorded as successful, so record the problem yourself before Raise Error, for example in a note or a log row.
-
Try / Catch does not catch Raise Error. Finally steps still run.
-
A failed precondition behaves like Raise Error, with the codes
UNAUTHORIZED_ACCESSorCONDITION_VIOLATIONunless you set your own.
Use stable, upper-case codes that callers can test for, such as CREDIT_EXCEEDED or MISSING_PRICE_LIST, and a message written for the person who will read it.
Halt ends the flow immediately without an error. Outputs set before it are returned, and changes the flow made before Halt are kept. Use it for early exits: “this order was already processed”, “nothing to do”. Try / Catch does not catch Halt; Finally still runs.
Errors in sub-flows
Section titled “Errors in sub-flows”When a flow calls another flow with Run Logic Flow:
- If the called flow fails unexpectedly (a step fails and nothing catches it), the Run Logic Flow step fails too. Put it inside Try to catch that error in the calling flow.
- If the called flow ends with Raise Error, or its precondition fails, the calling flow is not stopped, but it does not receive the error code, the message or any outputs. If the called flow declares outputs, the Run Logic Flow step fails with a technical error instead; inside Try, Catch receives that error, not your code.
So when a sub-flow can refuse to do its work, end it with Halt instead of Raise Error, after setting an output that says why (for example Succeeded or ResultCode), and check that output in the calling flow with a Condition. Outputs are returned after Halt.
Patterns
Section titled “Patterns”Log and continue. Sync each order line to the ERP; when one fails, record it and move on.
- Iterator
EachLineover the lines- Call Rest Api
PostLine, which sends the line to the ERP- Unexpected Error: Add Entity Annotation on the order with Title
Concatenate("Sync failed for ", EachLine.Item.productdescription)and Description[PostLine][error]
- Unexpected Error: Add Entity Annotation on the order with Title
- Call Rest Api
Always clean up. Use Finally for work that must happen whatever the outcome, such as stamping a “Last Sync Attempt” date or releasing a queue item you picked.
Validate, then refuse. Check inputs and data first with Conditions or a validation set, and Raise Error with a clear code before you change anything. A flow that refuses early leaves nothing half done.