Silent Saves Exposed: 8 Ways Your Power Apps Patch Can Fail Without Errors
Your Patch formula runs without a red squiggle, but the data never arrives. These eight root causes—from stale references to delegation delusions—will help you find the culprit in minutes.
The moment you deploy a Power Apps solution and hear, “The data isn’t saving,” your first instinct is to look for red underlines in the formula bar. But there are none. The Patch function ran without throwing an error, yet the record never materialized—no new row, no update, no trace.
Eight causes regularly lead to this silent failure. In this article I’ll walk through each one using a common scenario: a bug-tracking app with a BugList table. You’ll see the mistake, the symptom, and the fix. Along the way you’ll pick up debugging patterns that catch these issues before they reach your users.
Your Scenario: A Bug-Tracking App
Imagine you have a SharePoint list (or Dataverse table) named BugList with these columns:
| Column | Type | Notes |
|---|---|---|
Title | Text (required) | Bug name |
Severity | Choice: Low, Medium, High, Critical | |
AssignedTo | Lookup to Users | Person assigned |
Status | Choice: Open, In Progress, Resolved, Closed | |
EstimatedHours | Number | Hours to fix |
BugID | Auto‑number | Primary identifier |
Your app has a gallery (galBugs) that shows the current bugs, and a form panel for editing. The Selected item feeds into the update logic.
Many of the failures below stem from how you reference the record to patch. Let’s start with the most common.
Creating When You Meant to Edit
The single most frequent bug: you used Defaults(BugList) as the second argument when you intended to update an existing row.
Patch(BugList, Defaults(BugList), {
Title: txtTitle.Text,
Severity: { Value: "High" },
EstimatedHours: Value(txtHours.Text)
})If the user’s intention was to save changes to the bug currently selected in the gallery, the code above adds a new row with only those three fields populated. The record you thought you were updating stays untouched.
How to spot: Count the rows in BugList before and after the operation. If the count increased by one, this is your bug.
Fix: Pass the actual record you want to modify.
Patch(BugList, galBugs.Selected, {
Title: txtTitle.Text,
EstimatedHours: Value(txtHours.Text)
})Stale Reference After Gallery Refresh
Patch runs without a compile‑time error even when the record you’re trying to patch no longer exists—or points to a different row. This happens most often when you use galBugs.Selected in multiple Patch calls and the gallery requeries between them.
Patch(BugList, galBugs.Selected, { Status: { Value: "In Progress" } });
// ... gallery refreshes (e.g., after a filter change)
Patch(BugList, galBugs.Selected, { AssignedTo: varUser });The second Patch might not hit the same record, or Selected might be blank.
How to spot: Manually re‑select the item before the second operation—if that fixes it, you have a stale reference.
Fix: Capture the record into a variable immediately after the user selects it, then use that variable for all subsequent patches.
Set(varCurrentBug, galBugs.Selected);
Patch(BugList, varCurrentBug, { Status: { Value: "In Progress" } });
Patch(BugList, varCurrentBug, { AssignedTo: varUser });Silent Type Coercion That Drops Values
Power Fx tries to be friendly about types, but sometimes the data source refuses to accept a coerced value and silently discards it. A classic example is writing a Text property directly into a Number column.
Patch(BugList, galBugs.Selected, { EstimatedHours: txtHours.Text })txtHours.Text is a string. For a SharePoint list, this value may simply not be written—the rest of the patch succeeds, but EstimatedHours remains unchanged.
How to spot: Check the target column after the operation. If it stayed the same (or became blank), type coercion may be the cause.
Fix: Convert explicitly with Value() for numbers, DateValue() for dates, and Text() to force a string.
Patch(BugList, galBugs.Selected, { EstimatedHours: Value(txtHours.Text) })Always apply conversion when the source of a value is a TextInput, Label, or any control that outputs text—even if the column appears to accept strings.
Lookup and Choice Columns Expect a Specific Shape
Lookup columns are not strings, and choice columns cannot be simple text unless the connector expects that shape. AssignedTo is a lookup to the Users table. Patching it with an email address will fail silently.
Patch(BugList, Defaults(BugList), { AssignedTo: "jane@contoso.com" })How to spot: The AssignedTo field remains empty. If a required lookup is blank, the whole record may not save.
Fix for Dataverse / SharePoint lookups: Pass the related record obtained through LookUp.
Patch(BugList, Defaults(BugList), {
AssignedTo: LookUp(Users, Email = "jane@contoso.com")
})Fix for choice columns: Use the { Value: "…" } record shape. This applies to both SharePoint choices and Dataverse choices.
Patch(BugList, Defaults(BugList), { Severity: { Value: "High" } })Missing Required Fields on Create
When you create a row with Defaults(BugList), every required column must be provided. If Title is required and you omit it, the data source may reject the row without an error you can see.
Patch(BugList, Defaults(BugList), {
Severity: { Value: "Critical" }
}) // Title is required — rows not createdHow to spot: Wrap the Patch in IfError and display the result. This works with Dataverse; for SharePoint, the error may still be swallowed, but IfError catches what it can.
IfError(
Patch(BugList, Defaults(BugList), { Severity: { Value: "Critical" } }),
Notify("Failure: " & FirstError.Message, NotificationType.Error)
)Fix: Always supply every required field, even if you provide a placeholder value. Use a variable default when the form doesn’t collect them.
Patch(BugList, Defaults(BugList), {
Title: txtTitle.Text,
Severity: { Value: "Critical" }
})Delegation Limits Make Your Target Disappear
Using a Filter as the first argument of Patch is dangerous when the filter cannot be delegated. For example:
Patch(
Filter(BugList, Status.Value = "Open"), // not delegable
Defaults(BugList),
{ Title: "Mystery Bug" }
)Power Apps brings only the first 500 (or 2000) records locally. If the intended record is beyond that limit, Filter returns zero rows, and Patch ends up using Defaults—again creating a new row.
How to spot: The operation always creates a new row instead of updating the expected one.
Fix: Patch directly to the table and identify the record with a delegable LookUp or a primary key.
Patch(
BugList,
LookUp(BugList, BugID = varBugID), // delegable when BugID is indexed
{ Status: { Value: "Resolved" } }
)Always check the delegation status of every filter and lookup in a Patch call. If the source is wrapped in a non-delegable function, you’re patching against a local subset.
Permission Barriers That Don’t Surface in the App
The least‑obvious cause: the end user does not have permission to create or update records in the data source. The Patch runs in the app, but when the request reaches the data source, it’s denied.
How to spot: The Patch works in the maker’s session (who is an admin) but fails consistently for every other user. The permission change is the variable.
Fix: Verify that the user’s security role on the data source includes at least Create and Write privileges for the table. For SharePoint, that means the Contribute permission level; for Dataverse, assign the appropriate role with Write access to the table and record.
Network & Connector Hiccups with No Error Handling
Sometimes the data source is temporarily unavailable, the connector times out, or the network drops a packet. Without an IfError wrapper, the user never knows.
How to spot: Intermittent failures that you cannot reproduce. The Patch occasionally does nothing.
Fix: Wrap every critical Patch in IfError. Use the success branch to update the UI and the error branch to notify the user.
IfError(
Patch(BugList, varCurrentBug, { Status: { Value: "Resolved" } }),
Notify("Save failed. Please try again.", NotificationType.Error),
Notify("Bug updated.", NotificationType.Success)
)Build a Smart Save Pattern
The most effective way to avoid all these issues is to centralise your save logic. Encapsulate Patch inside a helper (if you use Component Libraries) or at least apply the same pattern every time:
- Capture the record in a variable.
- Convert all data types explicitly.
- Use the correct shape for lookups and choices.
- Include all required fields.
- Wrap in
IfErrorwith a meaningful error message.
This pattern alone catches every cause that throws an error, and it forces you to think about the rest before they become issues.
Quick Reference
| Cause | Telltale Sign | Immediate Action |
|---|---|---|
Defaults used for update | New row appears | Pass the existing record |
Stale Selected | Patch doesn’t affect the same record | Capture to variable first |
| Type mismatch | Column stays unchanged | Use Value(), DateValue(), etc. |
| Lookup/choice shape | Field is blank | Use LookUp or {Value: "..."} |
| Required field missing | Nothing saved | Include all required columns |
| Non-delegable filter | Unexpected create | Patch to source, target with LookUp |
| Permissions | Works for maker, not end user | Check security roles |
| Transient error | Intermittent no‑save | Wrap in IfError |
References
- Original article: “Patch() failures: the 8 reasons your record will not save” by PowerStack Editorial.
- For official details, see the Power Fx documentation on Microsoft Learn:
https://learn.microsoft.com/en-us/power-platform/power-fx/reference/function-patch(placeholder — confirm exact URL before linking). - Microsoft Learn:
IfErrorfunction overview (placeholder).