Power Apps error handling is a critical aspect of any canvas app build. Power Fx or other functionality may not always run as planned, so we should greet users with clear guidance. We may also need to track errors for ongoing monitoring & improvements.
In this article, I’ll go over some simple error handling techniques that you can leverage in your canvas apps.
Table of Contents
ToggleIntro
Power Apps error handling can take a few different forms, from a simple boolean true/false to fully customised messages and logging. Microsoft articles cover some of the concepts (here and here) and are good starting points to get familiar with the topic.
As some of you may know, formula-level error management was a long-time experimental/preview Power Apps feature. This is no longer the case, as of June 2024 it’s now fully GA (Generally Available).
If you’re a regular user of the Power Apps Code Review Tool (which you should be if you’re building canvas apps), you’ll know that error handling is an included pattern in the feedback:
Form controls
A common first step for new makers building data entry experiences in canvas apps is with the form control. These provide useful in-built error handling that’s easy to configure.
1- Use Form.Valid to show if a form is ready to be submitted. This will return false if errors are active or mandatory fields are still blank. Use this to set the Display Mode property of a Submit button:
// Analyse if form is ready for submission. Disable button until then.
If(
myForm.Valid,
DisplayMode.Edit,
DisplayMode.Disabled
)
2- Use the OnFailure property of a form control to provide a custom experience when a submission fails.
You’re able to configure multiple steps here, such as using the Notify function, sending an email, patching an Errors list or table and more:
I’ve previously covered form control error handling & functionality in more depth, article here.
IsBlank
The IsBlank function returns a boolean true or false value if a control or value is blank. Simply specify the control or value you want to analyse within the function:
This is great for evaluating controls or outputs before submitting data to a data source.
// Determine if control output is blank
IsBlank(txtInput.Text)
IfError
The IfError function will return a fallback value if a calculation fails. This may be a useful Power Apps error handling technique for invalid division calculations.
In this example, we have two text input controls set to accept numbers only. There’s a label underneath performing a calculation in its Text property:
Entering numbers that cannot be divided correctly will return an error:
The IfError function therefore provided a graceful fallback to a default value of our choice, should an error occur:
// Default to zero if calculation error occurs
IfError(
txtValue01.Text / txtValue02.Text,
0
)
You can also use IfError to evaluate other actions too, such as patching to a data source. We can customise the fallback to notify a user or add a record to an errors log:
IfError(
// If an error occurs with this patch
Patch(
Characters,
Defaults(Characters),
{Allegiance: "Dark Side"}
// Notify the user
),
Notify(
"Oops, something went wrong",
NotificationType.Error,
2000
)
)
IsError
The IsError function will detect for the presence of an error and return a boolean true or false value.
Using the same example as above, we can see the value change depending on whether the sum returns an error:
// Evaluate if calculation is an error
IsError(
txtValue01.Text / txtValue02.Text
)
We can combine IsError with other functions to evaluate other actions too, such as patching to a data source (though, I prefer the IfError approach for this scenario):
If(
IsError(
// If an error occurs with this patch
Patch(
Characters,
Defaults(Characters),
{Allegiance: "Dark Side"}
)
),
// Notify the user
Notify(
"Oops, something went wrong.",
NotificationType.Error,
2000
)
)
IsBlankOrError
When it comes to Power Apps error handling, IsBlankOrError is one of my favourites. It streamlines logic of two other functions – IsBlank and IfError – into a single function. The output is a boolean true or false.
In the example below, the control is blank (left) or has an error present in its Default property (right):
Using a valid reference in the Default property, or adding valid text to the input control will return false:
// Determine if control is blank or has an error present
IsBlankOrError(txtInput.Text)
Another good use for this function is to provide visual clues to users. For example, applying the logic to the BorderColor property of a text input control:
// Visual clue to user for text acceptance
If(
IsBlankOrError(Self.Text),
Color.Red,
Color.Black
)
App OnError
The OnError property is a great way to capture any unhandled errors that occur anywhere in your app. An unhandled error is any error occurring that isn’t using any of the Power Apps error handling techniques above.Â
To access this property, in design mode select App, then from the list of properties select OnError:
As this is capturing unhandled errors app-wide, we can utilise Power Fx to find the location of the error. Adding the following code to the OnError property will do just that:
// Find source of unhandled error
Notify(
$"{FirstError.Message}, Observed: {FirstError.Observed}, Source: {FirstError.Kind}",
NotificationType.Error,
2000
)
There are a couple of extra outputs in FirstError; Kind, HTTPResponse & HTTP StatusCode. I rarely see an output from the HTTP ones, but Kind will return an integer value relating to the error:
// Find source of unhandled error
Notify(
$"{FirstError.Message}, Observed: {FirstError.Observed}, Source: {FirstError.Source}, Kind: {FirstError.Kind}",
NotificationType.Error,
2000
)
The number relates to one of 27 ErrorKind values, as per this Power Apps error handling reference. It might not be a seamless experience to keep cross-checking ErrorKind values to the table shown, so I prefer to store either as a list, table or Named Formula that I can lookup to.
Here’s the Power Fx logic if you wish to add the Named Formula logic yourself:
// Error Kind logic
nfErrorKinds = Table(
{ErrorKindValue: 0, ErrorKindText: "None"},
{ErrorKindValue: 1, ErrorKindText: "Sync"},
{ErrorKindValue: 2, ErrorKindText: "MissingRequired"},
{ErrorKindValue: 3, ErrorKindText: "CreatePermission"},
{ErrorKindValue: 4, ErrorKindText: "EditPermissions"},
{ErrorKindValue: 5, ErrorKindText: "DeletePermissions"},
{ErrorKindValue: 6, ErrorKindText: "Conflict"},
{ErrorKindValue: 7, ErrorKindText: "NotFound"},
{ErrorKindValue: 8, ErrorKindText: "ConstraintViolated"},
{ErrorKindValue: 9, ErrorKindText: "GeneratedValue"},
{ErrorKindValue: 10, ErrorKindText: "ReadOnlyValue"},
{ErrorKindValue: 11, ErrorKindText: "Validation"},
{ErrorKindValue: 12, ErrorKindText: "Unknown"},
{ErrorKindValue: 13, ErrorKindText: "Div0"},
{ErrorKindValue: 14, ErrorKindText: "BadLanguageCode"},
{ErrorKindValue: 15, ErrorKindText: "BadRegex"},
{ErrorKindValue: 16, ErrorKindText: "InvalidFunctionUsage"},
{ErrorKindValue: 17, ErrorKindText: "FileNotFound"},
{ErrorKindValue: 18, ErrorKindText: "AnalysisError"},
{ErrorKindValue: 19, ErrorKindText: "ReadPermission"},
{ErrorKindValue: 20, ErrorKindText: "NotSupported"},
{ErrorKindValue: 21, ErrorKindText: "InsufficientMemory"},
{ErrorKindValue: 22, ErrorKindText: "QuotaExceeded"},
{ErrorKindValue: 23, ErrorKindText: "Network"},
{ErrorKindValue: 24, ErrorKindText: "Numeric"},
{ErrorKindValue: 25, ErrorKindText: "InvalidArgument"},
{ErrorKindValue: 26, ErrorKindText: "Internal"},
{ErrorKindValue: 27, ErrorKindText: "NotApplicable"}
);
Error
Perhaps slightly lesser known is the ability to create your own errors. We can do this by using the Error function.
In the example below, I have two Date Picker controls. Before submission, a check must occur to ensure the Start Date isn’t before the End Date. The OnSelect property of a button will perform the validation:
We can add a message & error kind to the Error function like so:
// Validate if start is before end, show error if so
If(
dtpStartDate.SelectedDate > dtpEndDate.SelectedDate,
Error(
{
Message: "Start date must be before end date",
Kind: ErrorKind.Validation
}
)
)
Common scenarios
You can use Power Apps error handling in all sorts of ways, so get creative and provide high-quality in-app experiences when things may not work as planned. The most common ones I use are below.
Patching to a single data source
As already shown above, it’s good practice to wrap error handling around any patch statements. I like to notify the user that something has gone wrong, but also use the Trace function to capture additional information that can be visible in Monitor. You could also patch a record to an errors table or send an email to admins:
IfError(
// If an error occurs with this patch
Patch(
Characters,
Defaults(Characters),
{Allegiance: "Dark Side"}
// Notify the user & send info to Monitor console
),
Notify(
"Oops, something went wrong",
NotificationType.Error,
2000
);
Trace(
$"Patching error logged at {Now()}",
TraceSeverity.Error,
{
OSType: Host.OSType,
SessionID: Host.SessionID,
Message: "Creation of record in Characters tbl has failed",
User: nfLoggedOnUser.displayName,
Screen: App.ActiveScreen.Name
}
)
)
Patching to successive data sources
You may want to patch a table, then patch another only if the first patch was successful. If it wasn’t successful, don’t process the 2nd patch.
You can use IfError again to implement this Power Apps error handling technique.
IfError(
// Try Patch 01
Patch(
Characters,
Defaults(Characters),
{
Title: txtNewCharacter01. Text,
Allegiance: "Dark Side"
}
),
// Notify if first Patch fails
Notify(
"First action failed",
NotificationType.Error,
2000
),
// Try Patch 02 if 01 was successful
Patch(
Characters,
Defaults(Characters),
{
Title: txtNewCharacter02. Text,
Allegiance: "Rebels"
}
),
// Notify if second Patch fails
Notify(
"Second action failed",
NotificationType.Error,
2000
)
)
Suppression
A regular occurrence when working with the Office 365 Users connector is blank user data. This might be a property not populated in the users profile, or they’ve not updated their profile image.
Calls to the connector always need a value passed in and will fail if an expected parameter is blank:
The API will continue to be called in your app unless a value is passed in. Alternatively, you can use error handling to suppress and provide a fallback.
In the example of a user image, that might look like:
// Show sample image if user image can't be resolved
IfError(
Office365Users.UserPhotoV2(cmbUserDirectory.Selected.Mail),
SampleImage
)
You could opt for using IsBlankOrError for this scenario too:
// Show sample image if user image can't be resolved
If(
IsBlankOrError(Office365Users.UserPhotoV2(cmbUserDirectory.Selected.Mail)),
SampleImage
)
Logging unhandled errors
In an attempt to continuously improve apps and general UX, it’s important to try & catch any unhandled errors. We can use the app OnError property to capture these by doing something like the following:
1- Get the error details using FirstError.
2- Display the information to the user via the Notify function.
3- Send information about the unhandled error to Monitor, using the Trace function.
4- Patch a dedicated ‘Unhandled errors’ SharePoint list.
All relevant actions run within the Concurrent function and, where required, have their own error handling applied. For logged-on user information & error kinds, I’m using a Named Formula:
With(
{ // Construct string to log error msg, source & kind
tvErrorLog: Concatenate(
FirstError.Message,
", Observed: ",
FirstError.Observed,
", Source: ",
FirstError.Source,
", Kind: ",
// Find ErrorKind text from Named Formula tbl
LookUp(
nfErrorKinds,
ErrorKindValue = FirstError.Kind
).ErrorKindText,
", DetailResponse: ",
FirstError.Details.HttpResponse,
", DetailCode: ",
FirstError.Details.HttpStatusCode
)
},
Concurrent(
// Notification to user
Notify(
tvErrorLog,
NotificationType.Error,
2000
),
// Send details to Monitor via Trace function
Trace(
$"Patching error logged at {Now()}",
TraceSeverity.Error,
{
OSType: Host.OSType,
SessionID: Host.SessionID,
Message: "Unhandled error",
User: nfLoggedOnUser.displayName,
Screen: App.ActiveScreen.Name,
ErrorLog: tvErrorLog
}
),
// Attempt to patch unhandled error details to SharePoint list
If(
IsError(
Patch(
'Unhandled Errors Log',
Defaults('Unhandled Errors Log'),
{
Title: tvErrorLog,
User: nfLoggedOnUser.displayName,
UserEmail: nfLoggedOnUser.mail,
DateTime: Now()
}
)
),
Trace(
$"Patching error log failed",
TraceSeverity.Warning
)
)
)
)
As a result, we can see details of the unhandled error in Monitor:
Also in the SharePoint list:
Even the most simplest of error handling can prove valuable to both makers and developers. As such, it should be regular techniques deployed to all apps to ensure errors are identified, resolved and fail gracefully.
Do you have any good examples of how you’re using error handling in your canvas apps or custom pages? If so, let me know in the comments.
Thanks for reading. If you liked this article and want to receive more helpful tips about the Power Platform, don’t forget to subscribe or follow me on socials 😊
Another brilliant article, Craig. Infact, I’ve actually adopted your version of the onError code as it’s more comprehensive than mine. I already had most of the parameters set up in this App I’m working on so was very straightforward to replace the elements like the SharePoint List for errors, logged in user as a Named variable etc. Using the ErrorKinds table was so cool. My code is looking much more robust. Thanks again for sharing.
This is very informative. and helped me a lot.
Did you get the difference between FirstError.Source and FirstError.Observed? I’m struggling here, I’ve tried learn.microsoft documentation but found nothing…They return bouth the control/control propriety?
Similar here tbh, don’t get a lot of difference & not much I can find online to help
First, this is an excellent summary and examples of key error-handling. Thanks for bringing it all together. Second, I finally found some info on the FirstError stuff, here:
https://learn.microsoft.com/en-us/power-platform/power-fx/reference/function-iferror#firsterror–allerrors.
Basically, .Source is where the error *actually* happened and .Observed is where it surfaced to (was observed by) the user.