To make it simpler, I will quote an article from Microsoft website:
Some queries against Microsoft Graph return multiple pages of data either due to server-side paging or due to the use of the $top query parameter to specifically limit the page size in a request. When more than one query request is required to retrieve all the results, Microsoft Graph returns an @odata.nextLink property in the response that contains a URL to the next page of results. More on that here – https://learn.microsoft.com/en-us/graph/paging
The Power Automate workflow below shows how to handle paging and extract all of the data (in my case, the display names of all users).
Important! This example is for Graph API only, any other API might structure data differently. The latter will require some adjustments in the workflow.
1) HTTP action. Peek code:
{ "inputs": { "method": "GET", "uri": "https://graph.microsoft.com/v1.0/users", "queries": { "$top": "4", "$select": "displayName" }, "authentication": { "type": "ActiveDirectoryOAuth", "tenant": "your_tenant_id_here", "audience": "https://graph.microsoft.com/", "clientId": "your_client_id_here", "secret": "your_secret_key_here" } } }
My development tenant has only 17 users, that is why I make it pull 4 users per request. That way I trigger paging. However, Graph API supports up to 999 entries per call before any paging.
2) Select action. From:
body('HTTP_-_Graph_API_-_Get_users')?['value']
Select:
item()?['displayName']
Some people use Parse JSON action before this step, I don’t, I drill down into data as is.
3) Initialize variable action – varAADUsers variable (Array type). Value:
body('Select_columns_-_Get_users')
4) Initialize variable action – varAADUsersNextLink variable (String type). Value:
body('HTTP_-_Graph_API_-_Get_users')?['@odata.nextLink']
5) Condition action – checks if varAADUsersNextLink variable is blank.
Please don’t confuse Null and Blank values. If there is no paging, then varAADUsersNextLink variable in Step 4 will have a blank value, not null.
If the system didn’t return any paging, then the workflow successfully finishes – varAADUsers has all entries in it.
6) Do until action. Edit in advanced formula:
@equals(body('HTTP_-_Graph_API_-_Get_more_users')?['@odata.nextLink'], null)
7) HTTP action. Peek code:
{ "inputs": { "method": "GET", "uri": "@variables('varAADUsersNextLink')", "authentication": { "type": "ActiveDirectoryOAuth", "tenant": "your_tenant_id_here", "audience": "https://graph.microsoft.com/", "clientId": "your_client_id_here", "secret": "your_secret_key_here" } } }
8) Set variable action. Value:
body('HTTP_-_Graph_API_-_Get_more_users')?['@odata.nextLink']
9) Select action. From:
body('HTTP_-_Graph_API_-_Get_more_users')?['value']
Select:
item()?['displayName']
10) Compose action. Inputs:
variables('varAADUsers')
11) Set variable action. Value:
union( outputs('Output_varAADUsers'), body('Select_columns_-_Get_more_users') )
RESULT
varAADUsers now contains the display names of all 17 users:
[ "Adele Vance", "Alex Wilber", "Diego Siciliani", "Grady Archie", "Henrietta Mueller", "Isaiah Langer", "Johanna Lorenz", "Joni Sherman", "Lee Gu", "Lidia Holloway", "Lynne Robbins", "Megan Bowen", "Miriam Graham", "Nestor Wilke", "Patti Fernandez", "Paul Bludov", "Pradeep Gupta" ]
Thanks for the really good instruction. But it only works for up to 5000 users, since that is the limit for an array in Power Automate.
Hey Simon,
Are you sure that’s the case? I am not aware of an array variable being limited to 5000 items.
In step 5 you caution that nextLink will be Blank and not null, but in step 6 you show checking for null. Can you confirm whether this check is correct?
Hey Mike,
Yes, it’s 100% correct. The reason the first one is compared to blank is because I already initialized a string variable. It’s blank (not null) if there was nothing to it. In the 2nd case, I check the property directly (without a variable). If it exists, then it’s something; if it doesn’t, then it’s null.
Neat! thanks for this very useful post
I used “Invoke an HTTP request” (the Azure connector) instead of [HTTP], as it uses the logged in user’s credentials. If all you’re after is fetching data from the Graph API, that connector saves the hassle of having to register an app and get a key for authentication…
Thanks, Linda!
I would still use an app as it gives us both an authentication (I am who I claim I am) and authorization (I can do what I am allowed to do) capabilities. The latter can be quite granular.
Awesome @Paul. This really helps.
I need one more help with below query. I’m trying to pull all AAD users details with ‘manager’ field
https://graph.microsoft.com:443/v1.0/users?$select=id,displayName,userPrincipalName,department,jobTitle&$expand=manager($select=id,displayName,mail)&$top=999
but I’m getting only 100 responses per page. Is it a graph API limitation with $expand query parameter? Is there any workaround?
Hi Anand,
Could be some limitation, but I don’t think anything changes in that case as you still get a “next” link that you can go through.
Hi @Paul,
As per the logic I also thought the same but still unable to get all results.
There are 43k users in the tenant. 100 response per page means I’ll get all responses in 430 pages.
As per your blog ‘doUntil’ iterate perfectly with count=300 resulting ~30k users (doUntil limits set to count=300 & timeout=PT1H) but as my http responses are large in number if I make count=450 or 500 flow runs infinitely.
Is there any workaround with doUntil condition to run successfully ?