When you build a canvas app that inserts and edits data in a SharePoint list, Patch() is one of the most important functions you’ll use. It gives you direct control over what gets saved, when it gets saved, and how you handle errors or special field types.
Power Apps Patch() with SharePoint: The Practical Mental Model (Create, Update, Upsert) + Full Code Patterns
When you build a canvas app that inserts and edits data in a SharePoint list, Patch() is one of the most important functions you’ll use. It gives you direct control over what gets saved, when it gets saved, and how you handle errors or special field types.
This article explains Patch() with a clear mental model, then shows complete, copy-paste-friendly formulas you can use in a blank canvas app.
1) What Patch() really does
Mental model:
“Patch writes an object (record) into a data source.”
- If you patch into
Defaults(DataSource)→ it creates a new item. - If you patch into an existing record (e.g.,
Gallery.Selected) → it updates that item. - Patch returns the saved record (useful to capture the created/updated item immediately).
Signature:
Patch(DataSource, BaseRecord, ChangeRecord1 [, ChangeRecord2, ...])
- DataSource: your SharePoint list (e.g.,
'Example List') - BaseRecord:
Defaults(...)for new, or an existing item record for update - ChangeRecord(s): fields you want to set
2) Why use Patch instead of SubmitForm()?
Patch is ideal when you want:
- A custom UI (not a Form control)
- Partial updates (only change specific fields)
- Multi-step saving (save as draft, then finalize)
- Better control of validation, error handling, and user messages
- Writing to multiple lists in one button press
Forms are ideal when:
- You want the quickest standard edit/new experience
- You don’t want to handle complex field shapes manually (People/Lookup/etc.)
Many real apps use both approaches, but Patch is the core skill that unlocks “serious” custom apps.
3) SharePoint columns used in this article (example schema)
Assume you have a SharePoint site:
https://contoso.sharepoint.com/sites/ExampleSite
…and a list:
- Example List
With these columns (common types):
| Column | Type |
|---|---|
| Title | Single line of text |
| ProjectNumber | Number |
| StartDate | Date |
| Status | Choice (e.g., New / In Progress / Done) |
| IsActive | Yes/No |
| Owner | Person (single) |
| Category | Lookup (single) |
| Tags | Choice (multi) |
You can rename to match your list; the Patch patterns stay the same.
4) The 3 Patch patterns you’ll use all the time
A) Create (new item)
Patch(
'Example List',
Defaults('Example List'),
{
Title: txtTitle.Text
}
)
B) Update (existing item)
Patch(
'Example List',
galItems.Selected,
{
Title: txtTitle.Text
}
)
C) Upsert (create OR update using one formula)
Patch(
'Example List',
If(IsBlank(varCurrentItem), Defaults('Example List'), varCurrentItem),
{
Title: txtTitle.Text
}
)
This “Upsert” is the cleanest pattern for a blank-canvas screen that can do New + Edit.
5) Full blank-canvas implementation (one screen)
This section is the “full code” part: formulas for a single-screen app with:
- A Gallery to browse items
- Inputs to edit fields
- Buttons: New, Save, Cancel
- A
varCurrentItemvariable to track “edit mode”
Controls (suggested names)
- Gallery:
galItems - Text inputs:
txtTitle,txtProjectNumber - DatePicker:
dpStartDate - Dropdown (Choice):
ddStatus - Toggle (Yes/No):
tglIsActive - ComboBox for People:
cmbOwner - Dropdown for Lookup:
ddCategory - ComboBox for multi-choice:
cmbTags - Buttons:
btnNew,btnSave,btnCancel
5.1 Screen OnVisible
// Reset selection when entering screen
Set(varCurrentItem, Blank());
Reset(txtTitle);
Reset(txtProjectNumber);
Reset(dpStartDate);
Reset(ddStatus);
Reset(tglIsActive);
Reset(cmbOwner);
Reset(ddCategory);
Reset(cmbTags);
5.2 Gallery Items
SortByColumns(
'Example List',
"ID",
Descending
)
5.3 Gallery OnSelect
When user clicks an item → load it into edit mode.
Set(varCurrentItem, ThisItem);
// Populate controls (simple approach: set defaults via Reset + DefaultSelectedItems/Default)
// If you use Default / DefaultDate / DefaultSelectedItems correctly,
// you can just Set(varCurrentItem, ThisItem) and call Reset() on inputs:
Reset(txtTitle);
Reset(txtProjectNumber);
Reset(dpStartDate);
Reset(ddStatus);
Reset(tglIsActive);
Reset(cmbOwner);
Reset(ddCategory);
Reset(cmbTags);
6) Default values for controls (so they “bind” to varCurrentItem)
6.1 txtTitle → Default
If(IsBlank(varCurrentItem), Blank(), varCurrentItem.Title)
6.2 txtProjectNumber → Default
If(IsBlank(varCurrentItem), Blank(), Text(varCurrentItem.ProjectNumber))
6.3 dpStartDate → DefaultDate
If(IsBlank(varCurrentItem), Today(), varCurrentItem.StartDate)
6.4 ddStatus (Choice) → Items
Choices('Example List'.Status)
ddStatus → DefaultSelectedItems
If(
IsBlank(varCurrentItem),
Blank(),
Filter(Choices('Example List'.Status), Value = varCurrentItem.Status.Value)
)
6.5 tglIsActive → Default
If(IsBlank(varCurrentItem), true, varCurrentItem.IsActive)
7) SharePoint complex field types (the part most people get wrong)
7.1 Person field (Owner)
ComboBox cmbOwner setup:
cmbOwner → Items:
Office365Users.SearchUser({ searchTerm: cmbOwner.SearchText })
cmbOwner → DisplayFields:
["DisplayName","Mail"]
cmbOwner → SearchFields:
["DisplayName","Mail"]
cmbOwner → DefaultSelectedItems (edit mode):
If(
IsBlank(varCurrentItem),
Blank(),
// varCurrentItem.Owner is a SharePoint person record
[ varCurrentItem.Owner ]
)
Patch shape for Person field (single):
{
'@odata.type': "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedUser",
Claims: "i:0#.f|membership|" & Lower(cmbOwner.Selected.Mail),
DisplayName: cmbOwner.Selected.DisplayName,
Email: cmbOwner.Selected.Mail
}
Tip: If you just want “current user” as owner:
{
'@odata.type': "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedUser",
Claims: "i:0#.f|membership|" & Lower(User().Email),
DisplayName: User().FullName,
Email: User().Email
}
7.2 Lookup field (Category)
ddCategory → Items (example using a separate list Categories):
Categories
ddCategory → DefaultSelectedItems:
If(
IsBlank(varCurrentItem),
Blank(),
Filter(Categories, ID = varCurrentItem.Category.Id)
)
Patch shape for Lookup:
{
'@odata.type': "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedReference",
Id: ddCategory.Selected.ID,
Value: ddCategory.Selected.Title
}
7.3 Multi-choice field (Tags)
cmbTags → Items:
Choices('Example List'.Tags)
cmbTags → DefaultSelectedItems:
If(IsBlank(varCurrentItem), Blank(), varCurrentItem.Tags)
Patch shape for multi-choice:
ForAll(
cmbTags.SelectedItems,
{ Value: ThisRecord.Value }
)
8) The Save button with full Patch (Create + Update + Error handling)
8.1 Simple validation example
Before Patch, validate required fields:
If(
IsBlank(Trim(txtTitle.Text)),
Notify("Title is required.", NotificationType.Error),
// else save
Set(
varSavedItem,
IfError(
Patch(
'Example List',
If(IsBlank(varCurrentItem), Defaults('Example List'), varCurrentItem),
{
Title: Trim(txtTitle.Text),
// Number (safe convert)
ProjectNumber: Value(txtProjectNumber.Text),
// Date
StartDate: dpStartDate.SelectedDate,
// Choice
Status: ddStatus.Selected,
// Yes/No
IsActive: tglIsActive.Value,
// Person (optional: only if selected)
Owner: If(
IsBlank(cmbOwner.Selected),
Blank(),
{
'@odata.type': "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedUser",
Claims: "i:0#.f|membership|" & Lower(cmbOwner.Selected.Mail),
DisplayName: cmbOwner.Selected.DisplayName,
Email: cmbOwner.Selected.Mail
}
),
// Lookup
Category: If(
IsBlank(ddCategory.Selected),
Blank(),
{
'@odata.type': "#Microsoft.Azure.Connectors.SharePoint.SPListExpandedReference",
Id: ddCategory.Selected.ID,
Value: ddCategory.Selected.Title
}
),
// Multi-choice
Tags: ForAll(
cmbTags.SelectedItems,
{ Value: ThisRecord.Value }
)
}
),
// OnError result:
Blank()
)
);
If(
IsBlank(varSavedItem),
Notify("Save failed. Check permissions, required fields, and field types.", NotificationType.Error),
Notify("Saved successfully.", NotificationType.Success);
Set(varCurrentItem, varSavedItem);
Refresh('Example List)
)
)
What this gives you:
- Works for New and Edit
- Strong handling of common SharePoint column types
- A saved-record variable (
varSavedItem) you can reuse immediately - Basic user messages
9) New and Cancel buttons
9.1 New button OnSelect
Set(varCurrentItem, Blank());
Reset(txtTitle);
Reset(txtProjectNumber);
Reset(dpStartDate);
Reset(ddStatus);
Reset(tglIsActive);
Reset(cmbOwner);
Reset(ddCategory);
Reset(cmbTags);
9.2 Cancel button OnSelect
If user was editing an existing item, you can revert the inputs back:
Reset(txtTitle);
Reset(txtProjectNumber);
Reset(dpStartDate);
Reset(ddStatus);
Reset(tglIsActive);
Reset(cmbOwner);
Reset(ddCategory);
Reset(cmbTags);
Notify("Changes discarded.", NotificationType.Information);
10) Common Patch mistakes (and quick fixes)
- Choice field saved as text instead of record
- Fix: use
ddStatus.Selected(record), notddStatus.Selected.Valueunless your column is plain text.
- Person field fails
- Fix: send the full person object shape with
Claimsas shown.
- Lookup field fails
- Fix: send the lookup record
{ Id: ..., Value: ... }with reference odata type.
- Multi-choice saved wrong
- Fix:
ForAll(cmbTags.SelectedItems, {Value: ThisRecord.Value})
- Numbers crash on blank
- Fix: wrap with
If(IsBlank(txtProjectNumber.Text), Blank(), Value(txtProjectNumber.Text))
Summary tables
Steps summary (what you implemented)
| Step | What you do | Result |
|---|---|---|
| 1 | Add SharePoint list as a data source | App can read/write list items |
| 2 | Use varCurrentItem to track edit vs new | Same screen supports New + Edit |
| 3 | Bind control defaults to varCurrentItem | Inputs auto-load selected item |
| 4 | Use Upsert Patch (Defaults vs existing record) | One Save button for both scenarios |
| 5 | Handle complex field shapes (Choice/Person/Lookup/Multichoice) | Patch works reliably with SharePoint types |
| 6 | Add IfError + Notify + Refresh | Clear feedback and stable UX |
Technical cheat-sheet
| Topic | Correct Patch pattern |
|---|---|
| Create | Patch(List, Defaults(List), { ... }) |
| Update | Patch(List, ExistingRecord, { ... }) |
| Upsert | Patch(List, If(IsBlank(var), Defaults(List), var), { ... }) |
| Choice | Status: ddStatus.Selected |
| Yes/No | IsActive: tglIsActive.Value |
| Number | ProjectNumber: Value(txtProjectNumber.Text) (guard blanks) |
| Person | SPListExpandedUser with Claims |
| Lookup | SPListExpandedReference with Id + Value |
| Multi-choice | ForAll(cmb.SelectedItems, {Value: ThisRecord.Value}) |
| Error handling | IfError(Patch(...), Blank()) + Notify |
