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):

ColumnType
TitleSingle line of text
ProjectNumberNumber
StartDateDate
StatusChoice (e.g., New / In Progress / Done)
IsActiveYes/No
OwnerPerson (single)
CategoryLookup (single)
TagsChoice (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 varCurrentItem variable 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)

  1. Choice field saved as text instead of record
  • Fix: use ddStatus.Selected (record), not ddStatus.Selected.Value unless your column is plain text.
  1. Person field fails
  • Fix: send the full person object shape with Claims as shown.
  1. Lookup field fails
  • Fix: send the lookup record { Id: ..., Value: ... } with reference odata type.
  1. Multi-choice saved wrong
  • Fix: ForAll(cmbTags.SelectedItems, {Value: ThisRecord.Value})
  1. Numbers crash on blank
  • Fix: wrap with If(IsBlank(txtProjectNumber.Text), Blank(), Value(txtProjectNumber.Text))

Summary tables

Steps summary (what you implemented)

StepWhat you doResult
1Add SharePoint list as a data sourceApp can read/write list items
2Use varCurrentItem to track edit vs newSame screen supports New + Edit
3Bind control defaults to varCurrentItemInputs auto-load selected item
4Use Upsert Patch (Defaults vs existing record)One Save button for both scenarios
5Handle complex field shapes (Choice/Person/Lookup/Multichoice)Patch works reliably with SharePoint types
6Add IfError + Notify + RefreshClear feedback and stable UX

Technical cheat-sheet

TopicCorrect Patch pattern
CreatePatch(List, Defaults(List), { ... })
UpdatePatch(List, ExistingRecord, { ... })
UpsertPatch(List, If(IsBlank(var), Defaults(List), var), { ... })
ChoiceStatus: ddStatus.Selected
Yes/NoIsActive: tglIsActive.Value
NumberProjectNumber: Value(txtProjectNumber.Text) (guard blanks)
PersonSPListExpandedUser with Claims
LookupSPListExpandedReference with Id + Value
Multi-choiceForAll(cmb.SelectedItems, {Value: ThisRecord.Value})
Error handlingIfError(Patch(...), Blank()) + Notify
Edvaldo Guimrães Filho Avatar

Published by