Power Apps ForAll Patch updates only the first record? Fix it
To patch multiple records, give Patch a table or loop with ForAll. If only the first record changes, the LookUp inside the loop compares a column with itself.
Power AppsDesign patterns10 min read
You tick several items in a gallery, press Done, and a formula loops over them with ForAll and Patch. Then only the first record in the data source changes. The others keep their old values. Power Apps can patch multiple records in one go. Microsoft's page on bulk records names this exact symptom, and the Patch reference explains the cause: inside the loop, the LookUp that should find each record compares a column with itself. This guide explains why that happens, the two fixes Microsoft documents, and when to drop the loop and give Patch a whole table instead.
The examples use a data source called Tasks with a number column Id and text columns Title and Status, and a collection called colChecked that holds the ticked tasks. Use your own names. The Patch reference says record shapes for column types such as Choice and Lookup vary by data source, and SharePoint's differ from Dataverse's. For SharePoint Person, Choice and Lookup columns, see how to patch Person, Choice and Lookup columns.
- A button runs
ForAllwithPatchover the ticked items, but only the first record in the data source changes. - The same record is saved once for every item in the loop, and the records you meant to change aren't touched.
- No error appears, because nothing in the formula is invalid.
Why only the first record changes
This formula looks right, and it's the one that fails:
ForAll(
colChecked,
Patch(
Tasks,
LookUp(Tasks, Id = Id),
{ Status: "Done" }
)
)
ForAll and LookUp each create a record scope. Inside the LookUp, the name Id means the Id of the Tasks record being checked. Microsoft's operators page explains the rule: field names added by a record scope override the same names from elsewhere. So Id = Id compares each task's Id with itself. That's true for every record, and LookUp returns the first one. Each pass of the loop patches that same first task.
Writing ThisRecord.Id on the right doesn't help. Learn says ThisRecord always refers to the innermost scope, which here is the LookUp, not the ForAll.
- Step 1ForAll takes one ticked row at a time
- Step 2LookUp(Tasks, Id = Id) reads both Ids from the same Tasks row
- Step 3Every task matches itself, so LookUp returns the first task
- Step 4Patch saves that first task on every pass
- Step 5Name the tables with As, and each LookUp finds its own task
Fix the LookUp: name each table with As
Microsoft's pages give two answers, and they don't agree:
- The bulk records page (Create or update bulk records, dated June 2021) uses the disambiguation operator,
colChecked[@Id], on the right-hand side. It says that without it, only the first record gets updated. - The Patch function reference (dated May 2026) shows a
ForAllandLookUpwritten withA[@OrderId]on the right, and says Power Apps currently reads both sides as fields of theLookUp's scope. So theLookUpstill finds the first row every time. Its fix is to name theLookUp's table withAs, which it recommends, or to putThisRecordon the left-hand side.
Our advice: use As. It follows the newer page and gives the same result the older page is aiming for. This is the Patch reference's pattern with our table names:
ForAll(
colChecked,
Patch(
Tasks,
LookUp(Tasks As T, T.Id = colChecked[@Id]),
{ Status: "Done" }
)
)
You can also name the loop's record. The ForAll reference says As can name the record being processed, and the operators page says giving each scope its own name makes every scope available in nested formulas. With both tables named, there's nothing left to guess:
ForAll(
colChecked As Checked,
Patch(
Tasks,
LookUp(Tasks As T, T.Id = Checked.Id),
{ Status: "Done" }
)
)
These follow Microsoft's documented syntax; we haven't run them in a tenant. Test with two or three records and check that each one changes.
When the records already come from the data source, skip LookUp
If you loop over records that came from the data source itself, each record can be the base record for Patch, so there's nothing to look up. Microsoft's operators page shows this shape, with With naming a filtered table first:
With(
{ Overdue: Filter(Tasks, Status = "Overdue") },
ForAll(
Overdue As Task,
Patch(Tasks, Task, { Status: "Escalated" })
)
)
The loop runs over the filtered table, not over Tasks itself. The ForAll reference says you can't change the table that ForAll is looping over.
When the items are ticked in a gallery
Learn says the disambiguation operator can't be used on a gallery's items. Its alternative adds a label to the gallery template, bound to the Id column, and compares with that label. Here the label is lblId, the checkbox is chkDone and the gallery is galTasks:
ForAll(
Filter(galTasks.AllItems, chkDone.Value = true),
Patch(
Tasks,
LookUp(Tasks, Id = lblId.Text),
{ Status: "Done" }
)
)
Learn's sample compares the Id with the label's text, as above. If your Id column is a number, such as SharePoint's ID, wrap the label in Value() so both sides are numbers: LookUp(Tasks, Id = Value(lblId.Text)).
Learn mentions a third way without a formula: when you make the local copy of your data, use AddColumns to add a renamed copy of the Id column (Learn calls it NewId). The loop then compares NewId with Id, and the two names can't collide.
Patch a table of records in one call
Patch doesn't need ForAll to change several records. The Patch reference documents a form that takes a table of base records and one or more tables of change records:
- The records in each change table correspond one for one with the base records.
- Each change table must have the same number of records as the base table, or an error occurs.
- When a base record came from the data source, Patch finds and modifies it. For a single record, Learn says you must be able to trace it back to the data source, for example through a gallery's Items or a context variable. It doesn't say whether a collection keeps that link, so test with two or three records.
- The result is a table, with each record corresponding one for one with the base and change records.
To build the change table, use ForAll inside Patch. The ForAll reference says it returns its results in the same order as the input table, so each change lines up with its record:
IfError(
Patch(
Tasks,
colChecked,
ForAll(colChecked, { Status: "Done" })
),
Notify("Some tasks weren't updated: " & FirstError.Message, NotificationType.Error)
)
Microsoft's code optimization page calls this order the better approach: Patch on the outside, with ForAll building the records inside it, instead of ForAll around Patch. Its own sample passes the ForAll table to Patch as the second argument, Patch(DataSource, ForAll(...)); the version above uses the reference's documented three-argument form. It also leaves no LookUp to go wrong. The IfError wrapper follows the Patch reference's own example for patching several records.
The bulk records page also shows a shorter form, Patch( DataSource, Collection ), for when the collection matches the data source. Its example is two collections with the same column names. The reference's syntax section only lists the form with change tables, so if the short form misbehaves in your app, use the one above.
Create many records at once
To create records, the bulk records page loops with ForAll and patches Defaults, which gives a new base record each time:
ForAll(
colNewTasks,
Patch(
Tasks,
Defaults(Tasks),
{ Title: Title, Status: Status }
)
)
The Patch reference does the same in one call, with a table of Defaults records and a table of changes of the same length:
Patch(
Tasks,
Table(Defaults(Tasks), Defaults(Tasks)),
Table(
{ Title: "Order the labels", Status: "Pending" },
{ Title: "Book the room", Status: "Pending" }
)
)
Which pattern to use
| Your situation | Use |
|---|---|
| The records came from the data source and all get the same change | One Patch with a table, with ForAll building the change records |
| The records to change match a condition | With and Filter, then ForAll patching each record |
| Your collection only holds a copy of each Id | ForAll with LookUp, both tables named with As |
| The items are ticked in a gallery, with no collection | ForAll over the gallery's AllItems, comparing with a label |
| New records | ForAll with Defaults, or one Patch with a table of Defaults |
Delegation and performance
These notes are only what Microsoft's pages state:
- Patch itself isn't delegated, and doesn't need to be. The Patch reference says it writes rather than queries. A delegation warning on a formula with
Patchcomes from the part that picks the records:Filter,LookUporForAll. - ForAll can't be delegated. When it runs over a data source, only the first part of the data is retrieved before the formula runs. Learn's page on data sources puts that first part at 500 records by default, and up to 10,000 if you raise the limit in the app's settings. So
ForAll(Tasks, ...)straight over a large table only reaches its first records. Narrow the records with a delegableFilterfirst, and keep the table you loop over small. - A LookUp inside a loop is one query per record. Microsoft's performance guidance calls this the Select N+1 pattern: one query for the list, then one more for each item. It's another reason to prefer one
Patchwith a table. - Don't depend on the order. The ForAll reference says records can be processed in any order and, when possible, in parallel. Microsoft's code optimization page says the opposite: that ForAll looks at one record at a time. Either way, don't write a loop whose result depends on the order, such as numbering records as you go.
UpdateContext,ClearandClearCollectcan't be used insideForAll.
For a change that should reach every matching record in a big list, our delegation cheat sheet covers UpdateIf, RemoveIf and when a flow is the better tool.
Do
- Name both tables with
Aswhenever aLookUpsits inside aForAll. - Prefer one
Patchwith a table when the records came from the data source. - Wrap the bulk
PatchinIfErrorand tell the user which save failed. - Test with two or three records and check that each one changed.
Don't
- Don't write
LookUp(Tasks, Id = Id)orId = ThisRecord.Idinside a loop: both compare the column with itself. - Don't change the table that
ForAllis looping over. - Don't run
ForAllstraight over a large data source and expect it to reach every record.
If the bulk save fails with a banner rather than changing the wrong record, see The requested operation is invalid. For a slow app overall, start with canvas app performance.
Sources
- Microsoft Learn: Create or update bulk records in Power Apps
- Microsoft Learn: Patch function: modify or create a set of records, delegation, and the use of As or ThisRecord
- Microsoft Learn: ForAll function
- Microsoft Learn: Operators and identifiers: ThisRecord, As and the disambiguation operator
- Microsoft Learn: Power Apps code optimization: optimize ForAll
- Microsoft Learn: Understand data sources for canvas apps: working with large data sources
- Microsoft Learn: Identify and mitigate canvas app performance issues: Select N+1 data queries
Add LowCodeStacks as a preferred source on Google
Was this helpful?
Comments
No comments yet. Share what worked for you.
Sign in to comment. Comments show under a display name you choose, never your email.