Batch Create and Update SharePoint Items in Power Automate with the $batch API
In this series: Power Automate gotchas
The standard Apply to each pattern is fine for twenty SharePoint items. It becomes painful when a nightly flow needs to create or update thousands of rows and every item costs another connector round trip. SharePoint's batch endpoint lets Power Automate send many writes in one HTTP request, if you build the multipart body correctly.
The batch endpoint reduces connector chatter
The SharePoint REST batch endpoint accepts multiple operations in one call. In Power Automate, use Send an HTTP request to SharePoint and post to _api/$batch. The request body is multipart MIME. For create and update operations, the writes sit inside a changeset.
The payoff is round-trip reduction. Instead of one HTTP action per item inside Apply to each, you build a batch body for a group of items, send it once, parse the response, and continue with the next group.
| Approach | Connector calls for 1000 updates | Typical behavior |
|---|---|---|
| Apply to each with Update item | 1000 | Simple but slow |
| Apply to each with concurrency | 1000 | Faster but can throttle |
| REST batch with 10 groups | 10 | Faster and more controlled |
| REST batch plus per-item parsing | 10 | Best operational visibility |
Batching is not magic parallelism. It is a more efficient HTTP contract. You still need limits, retry handling, and a way to identify which item failed.
Changesets are the unit for writes
A batch can contain one or more changesets. For SharePoint writes, keep the design simple: one changeset per batch request, and a practical maximum around one hundred operations per changeset. SharePoint and service limits change over time, but a conservative design avoids payload size and timeout problems. Across a full job, process up to roughly one thousand operations at a time before checkpointing.
The body has two boundaries: one for the batch and one for the changeset. Each operation has its own content ID, method, URL, headers, and JSON payload.
--batch_123
Content-Type: multipart/mixed; boundary=changeset_456
--changeset_456
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1
POST https://contoso.sharepoint.com/sites/ops/_api/web/lists/getbytitle('Orders')/items HTTP/1.1
Content-Type: application/json;odata=verbose
{ "__metadata": { "type": "SP.Data.OrdersListItem" }, "Title": "Order-1001", "Status": "New" }
--changeset_456
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2
PATCH https://contoso.sharepoint.com/sites/ops/_api/web/lists/getbytitle('Orders')/items(42) HTTP/1.1
Content-Type: application/json;odata=verbose
IF-MATCH: *
X-HTTP-Method: MERGE
{ "__metadata": { "type": "SP.Data.OrdersListItem" }, "Status": "Processed" }
--changeset_456--
--batch_123--
The blank lines are part of the protocol. Multipart bodies are unforgiving. A missing boundary, missing blank line, or incorrect content type can turn a good design into a cryptic bad request.
Build the body from an array, not string chaos
Power Automate can assemble the batch body in a controlled loop. Start with an array of normalized operations. Each operation should include an operation ID, method, relative endpoint, entity type, payload, and source record key. Then chunk the array into groups.
Use variables for the boundaries so the header and body agree:
{
"batchBoundary": "batch_20260430_001",
"changeSetBoundary": "changeset_20260430_001",
"operationsPerBatch": 100,
"listEntityType": "SP.Data.OrdersListItem"
}
Prefer predictable content IDs. Set Content-ID to the operation index or source row number. When the response comes back, you can map failures to business records instead of guessing from position.
For updates, use IF-MATCH: * only when overwriting the latest item is acceptable. If concurrency matters, retrieve and pass the expected ETag. Batching should not weaken your data rules.
The HTTP action needs exact headers
Send the request to the site that owns the list. The action URI should be _api/$batch. The method is POST. The Content-Type header must include the batch boundary. The Accept header should match the OData flavor you are using.
Method: POST
Uri: _api/$batch
Headers:
Accept: application/json;odata=verbose
Content-Type: multipart/mixed; boundary=batch_20260430_001
Body:
multipart batch body
Keep authentication inside the connector. The SharePoint HTTP action handles the connection. Do not manually store tokens in the flow. If you need application permissions or cross-site operations, evaluate a custom connector or Azure-hosted worker instead of forcing secrets into actions.
Response parsing is where production quality shows
A batch response can contain mixed success and failure. Do not treat the outer HTTP status as the only result. Parse each operation section, read the status code, and map it back to Content-ID. A batch can return success for the request while one item failed validation.
| Response code | Meaning | Handling |
|---|---|---|
| 201 | Item created | Mark source as created |
| 204 | Item updated | Mark source as updated |
| 400 | Bad payload or field value | Log item failure |
| 404 | List or item not found | Stop batch and investigate |
| 429 | Throttled | Retry batch with backoff |
| 500 | Server failure | Retry if safe |
Log per item, not only per batch. Store the source key, Content-ID, HTTP code, and response message. If one payload has an invalid choice value, operations should fix that row rather than rerun the whole job blind.
Batching changes the performance conversation
The speed-up can be dramatic because latency dominates small writes. In many tenant environments, simple item updates inside Apply to each take hundreds of milliseconds each before business logic is considered. Batching compresses the overhead.
| Workload | Apply to each | Batch API | Practical gain |
|---|---|---|---|
| 100 creates | 2 to 5 minutes | 10 to 25 seconds | Strong |
| 1000 updates | 20 to 45 minutes | 2 to 6 minutes | Very strong |
| 5000 mixed writes | Often unreliable | Chunked job | Operationally manageable |
Measure in your tenant. List complexity, column types, flows running at the same time, and throttling all change the numbers. The point is not a universal benchmark. The point is that fewer connector round trips usually wins.
Use SharePoint batching when the process is truly bulk-oriented and you can own the error handling. For small lists and simple flows, the standard connector is easier. For nightly syncs, migrations, and high-volume status updates, _api/$batch is the difference between hoping the loop finishes and engineering a job that actually does.
Keep reading
Filter Rows: The Lookup GUID Is _parentcustomerid_value
Filter rows is Dataverse OData. Lookups filter on _logicalname_value, not the name. Filter array runs after the download, and @equals does not belong in that box.
Get a Row by ID: Missing Fails the Run, Empty List Rows Does Not
Get a row by ID fails the run when the GUID is missing. List rows with zero matches succeeds. Look up the business key and branch on empty.
When an HTTP Request Is Received: The Callback URL Is a Secret
The HTTP trigger URL carries a SAS signature. Anyone with it can start the flow. Restrict who can call, always Respond, and never treat that URL as public.
Power Automate Do Until: The Loop That Succeeds After Giving Up
A Do until that hits count or timeout still succeeds. Put Get inside the loop, add a Delay, and test the condition after the loop exits.
Power Automate Trigger Conditions: The Run That Never Appears
A trigger condition that is false does not start a run and does not leave run history. How the expression differs from a Condition action, and how to see the skipped checks.
Power Platform September 2026 Update: The App-Builder Skill, Agent Plugins, and the Refreshed Model-Driven UI
What matters in Microsoft's September 2026 Power Platform feature update: the model app-builder skill goes GA, the canvas authoring agent plugin goes GA, generative pages embed in forms and reach connectors, model-driven header refresh is GA with display density in preview, and Power Automate gets server-side search.
Newsletter
New posts, straight to your inbox
One email when a new post goes up. Double opt-in, unsubscribe anytime. Not a daily digest.
Comments
- No comments yet. Be the first.