Array & Collection Actions
Power Automate's built-in Data Operations actions are the most underused feature by beginners. These are the most important ones to master. This guide explains each one clearly so you can work with lists of data without fighting loops.
Arrays in Power Automate
An array is simply a list of values or objects. In Power Automate, arrays appear constantly: SharePoint Get Items returns an array of list items. Forms Get Responses returns an array of submissions. HTTP APIs almost always return arrays in their response bodies.
In JSON (the data format Power Automate uses internally), arrays look like this:
Simple array of strings
["Alice", "Bob", "Charlie"]
Array of objects (typical SharePoint Get Items result)
[
{ "Id": 1, "Title": "Invoice Q1", "Amount": 1500, "Approved": false },
{ "Id": 2, "Title": "Invoice Q2", "Amount": 2300, "Approved": true },
{ "Id": 3, "Title": "Invoice Q3", "Amount": 800, "Approved": false }
]Every action in the Data Operations category: Select, Filter Array, Compose, Create HTML Table, Create CSV Table and Join. Each, Filter Array, Compose, Create HTML Table, takes an array as its primary input and returns a transformed result.
Apply to Each
Apply to Each is a Control action that loops over every item in an array and runs a set of child actions for each one. It is the most common way to process multiple records.
Basic usage
Drag in an Apply to Each, then in the "Select an output from previous steps" field pick the array : for example the value output from Get Items. Then add actions inside the loop. Inside those actions, use items('Apply_to_each') or the dynamic content "Current item" to access the current record.
Reading fields inside the loop
Current item Title: items('Apply_to_each')?['Title']
Current item Amount: items('Apply_to_each')?['Amount']
Current item Author: items('Apply_to_each')?['Author']?['Email']Concurrency: Running iterations in parallel
By default, Apply to Each runs one iteration at a time (sequential). If your loop iterations are independent, enable concurrency in the action settings. You can run up to 50 iterations simultaneously, dramatically speeding up large loops.
Do not enable concurrency if iterations write to the same variable or depend on each other's output : race conditions will corrupt your data. It is safe only when each iteration is fully independent.
Select: Transform every item in an array
Select is a Data Operations action that maps over every item in an array and produces a new array where each item has been transformed. Think of it as "for every item, reshape it into this new form."
Select takes two inputs: the array to process (From), and a key/value mapping (Map) that defines the shape of each output item.
Example: extracting only the fields you need
SharePoint Get Items returns every field on every item : often 30 or more fields you don't need. Use Select to trim the array down to just the fields you care about.
Select Map configuration
From: body('Get_items')?['value']
Map:
Key: Title Value: items()?['Title']
Key: Email Value: items()?['Author']?['Email']
Key: Amount Value: items()?['Amount']Result: A clean array with only the fields you mapped
Input
[
{ Title: "Invoice Q1", Email: "a@co.com", Amount: 1500 },
{ Title: "Invoice Q2", Email: "b@co.com", Amount: 2300 }
]Output
[
{ "Title": "Invoice Q1", "Email": "a@co.com", "Amount": 1500 },
{ "Title": "Invoice Q2", "Email": "b@co.com", "Amount": 2300 }
]Example: flattening a nested field
Select can also compute new values using expressions in the value field.
Select Map: Adding a computed field
Key: FullName Value: concat(items()?['FirstName'], ' ', items()?['LastName']) Key: TaxAmount Value: mul(items()?['Amount'], 0.2) Key: DueDate Value: addDays(items()?['InvoiceDate'], 30)
Select runs entirely server-side in a single step. It is vastly faster than an Apply to Each loop that reads fields one by one. For any scenario where you just need to reshape an array, always prefer Select over a loop.
Filter Array
Filter Array keeps only the items in an array that match a condition. It takes an array as input and outputs a smaller array containing only the matching items.
Basic mode: The visual condition builder
In the designer, Filter Array shows a condition builder similar to the Condition action. Set the left side to a field from the array item, pick an operator, and type or select the value to compare against.
Filter Array: Keep only approved invoices
From: body('Get_items')?['value']
Condition:
item()?['Approved'] is equal to trueAdvanced mode: Writing the condition as an expression
Click "Edit in advanced mode" to write the full condition as a single boolean expression. This gives you access to all logical and comparison functions.
Advanced mode expression
@and( equals(item()?['Status'], 'Active'), greater(item()?['Amount'], 1000) )
Note the use of item() (no action name in brackets) inside Filter Array. This refers to the current item being evaluated : it is different from items('Apply_to_each') which is used inside loop actions.
What to do with the filtered result
The output of Filter Array is still an array. You can feed it into:
Apply to Each: loop over the filtered items Select: reshape the filtered items Create HTML Table: render them as a table length(): count how many matched empty(): check if any matched at all
Example: filtering invoices over £1000 with status Active
Input
[
{ "Title": "Invoice Q1", "Amount": 800, "Status": "Active" },
{ "Title": "Invoice Q2", "Amount": 2300, "Status": "Active" },
{ "Title": "Invoice Q3", "Amount": 1500, "Status": "Archived" }
]Output
[
{ "Title": "Invoice Q2", "Amount": 2300, "Status": "Active" }
]Compose
Compose is a simple but incredibly useful action. Its only job is to evaluate an expression and store the result so you can reference it later. Think of it as a named constant or an intermediate variable without the overhead of a proper Variable action.
Using Compose to avoid re-typing expressions
If you need the same computed value in five different places, put it in a Compose action once and reference the output: outputs('Compose').
Compose: Compute once, use everywhere
Compose input:
formatDateTime(utcNow(), 'yyyy-MM-dd')
Later in the flow:
outputs('Compose_Date') → "2026-05-17"Building a JSON object with Compose
Compose can hold any type : a string, number, boolean, array, or full JSON object. This is perfect for building a request body before sending it to an HTTP action.
Compose : building an HTTP request body
{
"Title": "@{triggerBody()?['Title']}",
"SubmittedBy": "@{triggerBody()?['Author']?['Email']}",
"SubmittedAt": "@{utcNow('yyyy-MM-ddTHH:mm:ssZ')}",
"Score": @{int(triggerBody()?['Score'])}
}Use Compose to debug expressions. Point it at any expression, run the flow, and inspect the output in the run history to see exactly what value it resolved to.
Parse JSON
When Power Automate receives a string that contains JSON : from an HTTP response, a SharePoint multi-line field, or a webhook body : it treats it as plain text. Parse JSON turns that string into a typed object so you can access its fields using the dynamic content picker instead of writing expressions.
How it works
Parse JSON takes a Content input (your JSON string) and a Schema (the shape of the data). Once parsed, every field in the schema appears as a dynamic value in subsequent actions.
Parse JSON: Input
Content: body('HTTP_Request')
Schema (auto-generated or written manually):
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"active": { "type": "boolean" },
"tags": { "type": "array", "items": { "type": "string" } }
}
}Generating the schema automatically
Run the flow once to get a sample response, then click "Generate from sample" in the Parse JSON action, paste the sample, and the schema is built for you automatically.
If the actual data structure doesn't match the schema, the Parse JSON action will fail. If your API sometimes returns extra or missing fields, make the schema more permissive by removing required field declarations or using additionalProperties: true.
When to use it
HTTP connector response body: always needs Parse JSON SharePoint multi-line JSON fields: if you store JSON there Webhook payloads: if body arrives as a string Compose outputs that are objects: to get typed field access
Create HTML Table and Create CSV Table
Both actions take an array of objects and format them as a table. They are most useful when building email bodies or file outputs.
Create HTML Table
Takes an array, produces a fully formatted HTML table string. Use it in the body of a Send Email action with HTML enabled.
Create HTML Table
From: outputs('Select') (use Select first to pick the columns you want)
Columns: Automatic (uses the keys from your objects as column headers)
OR Custom (define your own column names and value expressions)The output: A complete HTML table string
<table>
<thead><tr><th>Title</th><th>Amount</th><th>Status</th></tr></thead>
<tbody>
<tr><td>Invoice Q1</td><td>1500</td><td>Active</td></tr>
<tr><td>Invoice Q2</td><td>2300</td><td>Active</td></tr>
</tbody>
</table>Always run Select before Create HTML Table to control which columns appear and in what order. If you feed a raw Get Items result into Create HTML Table you will get 30+ SharePoint system columns in your table.
Create CSV Table
Identical to HTML Table but produces a CSV string instead. Combine with Create File to generate downloadable CSV reports in OneDrive or SharePoint.
Create CSV Table output
Title,Amount,Status Invoice Q1,1500,Active Invoice Q2,2300,Active
Join
Join turns an array into a single string by inserting a separator between each item. It is the Data Operations equivalent of the join() expression function.
Join: Array to comma-separated string
Input
["Finance", "IT", "HR"] Separator: ", "
Output
"Finance, IT, HR"
A common use case is building a comma-separated list of approvers from a people field array, or joining tags for a SharePoint metadata field.
Practical example: Building a recipient list
From: outputs('Select_Emails') (array of email strings from Select)
Separator: ";"
Result: "alice@co.com;bob@co.com;charlie@co.com"
Use as: the To field of Send an EmailNested loops and performance
A nested loop is an Apply to Each inside another Apply to Each. They are sometimes necessary but always worth trying to avoid.
Why nested loops are slow
Each iteration of the outer loop triggers a full run of the inner loop. For 100 outer items each with 10 inner items, your flow executes 1,000 iterations plus all the action calls inside them. At scale this quickly hits Power Automate's 5,000 action limit per run or causes timeout errors.
Before building a nested loop, ask: can I use a single Filter Array or Select on the full dataset instead? These actions run in a single step and process the entire array server-side with no per-iteration overhead.
When nested loops are unavoidable
Acceptable: Outer loop over 5-10 parent records, inner loop over a few children each
Risky: Outer loop over 50+ records with connector calls in the inner loop
Dangerous: Any loop containing API calls that could be rate-limited (throttling)
Mitigation: Enable concurrency on the outer loop
Pre-fetch all child data before looping and Filter Array inside the loop
Use OData filter queries to reduce Get Items results before loopingUsing Filter Array instead of a loop with a condition
A very common anti-pattern is an Apply to Each containing a Condition action that skips most items. Replace this with Filter Array before the loop, then only loop over the items you actually need to process.
Anti-pattern: Loop with internal condition
Apply to Each (all 500 items)
> Condition: item Status equals Active
Yes: Update SharePoint item
└── No: Do nothingBetter approach: Filter first, then loop
Filter Array (all 500 items → only Active ones, e.g. 12) Apply to Each (only the 12 Active items) > Update SharePoint item
Choosing the right action
Here is a quick reference for picking the right tool for each scenario:
| Goal | Use this |
|---|---|
| Loop over items and call an API or connector for each | Apply to Each |
| Reshape every item (pick fields, compute values) | Select |
| Keep only items matching a condition | Filter Array |
| Store a computed value for re-use | Compose |
| Get typed access to a JSON string's fields | Parse JSON |
| Build an HTML table for an email body | Create HTML Table (after Select) |
| Export data to a CSV file | Create CSV Table (after Select) |
| Build a semicolon or comma-separated string from an array | Join |
| Count items in an array | length() expression |
| Check if an array is empty | empty() expression |
Live Array Sandbox
See exactly what Select, Filter Array, Join, and Compose produce on a real array. Edit the input array or the action settings and the output updates instantly.
Select: Map
Available fields: , , ,
Output
5 items[
{
"Title": "Invoice Q1",
"Amount": 1500
},
{
"Title": "Invoice Q2",
"Amount": 2300
},
{
"Title": "Invoice Q3",
"Amount": 800
},
{
"Title": "Invoice Q4",
"Amount": 3100
},
{
"Title": "Invoice Q5",
"Amount": 450
}
]Power Automate equivalent
Data Operations > Select
From: body('Get_items')?['value']
Build OData filters for Get Items
Use the OData Filter Builder to write filter queries for SharePoint and Dataverse Get Items : reducing the data before it even reaches your flow. This is the most effective way to avoid large loop iterations.