If you are looking to automatically add comments to Microsoft List Items (or “SharePoint” List items) via Power Automate, you’ve come to the right place. In this article, I will show you how you can use the SharePoint Rest API to both post a comment and read ALL comments on a list item. Surprisingly, adding a comment is a single action, reading all comments into a convenient format when there is tagging involved, is rather complex as tagging is handled by a separate array.
The SharePoint REST API is well documented here as are the Microsoft List comment features, like the ability to tag and therefore trigger an email, notifying the user of a mention in a list comment. The email is a feature of Microsoft Lists and therefore does not depend on a Flow. If you create a comment on a list item and tag a colleague, they will be emailed with a link direct to the associated item.
Adding a comment
Via the “Send an HTTP Request to SharePoint” trigger, you simply select the site that you wish to post a comment to, and then via the URI, define the List and List Item. Below you can see that the List Name is defined as ‘Issue Tracker’, the item ID, in this case, is dynamic but is based on the unique item ID.
_api/web/lists/getbytitle('LISTNAME')/items('ITEMID')/Comments
The body of the action is based on the text and optional mentions, defined by multiple objects of an array. In the above, we see that the first email is referenced by object 0 i.e. @mention{0} because objects are defined by integers, starting from 0. Below is a sample payload for you to copy and use in your flow.
{
"text": "This is a new item @mention{0}. Please take a look ASAP!",
"mentions": [
{
"email": "AnEmail@YourTenant.onmicrosoft.com"
}
]
}
Possible use cases for this include tagging users in the progress of an approval flow or highlighting list items that meet certain criteria. You could even use adaptive cards to request a comment and further tag colleagues in the progress. A sample of comments on a list item can be seen below.
Read ALL comments
This is very much an identical process, albeit the body is empty and the HTTP API Method is GET. As before, make sure you send the List Name and Item ID via the URI and the API will return a body where the comment text and mentions are contained with a selection of nested arrays that you can see below.
Turning this into something meaningful is worth a blog post of its own and it stumped me when producing the video demo, to be honest. Manipulating and re-purposing JSON is a good challenge though and so I came up with the following solution.
FROM: body('Send_an_HTTP_request_to_SharePoint')?['body']?['d']?['results']
TEXT: concat(formatdatetime(item()?['createdDate'],'dd-MM-yy HH:mm'),' ',item()?['text'])
MENTION: item()?['mentions']?['results']
With the body of the HTTP action as the source and using a Select Action, I have defined two keys, the Text and the Mentions (we will re-purpose in an apply to each step next). The expressions for both are supplied above. For the text, I concatenate the created date (and format it to the UK DateTime format) and combine it with the comments text. For the mentions, I grab the results array for use in the next step. I now end up with an array similar to below. Note that in each mention ” @mention{0}″ there is an incremental integer starting from 0, we use this last on.
"body": [
{
"Text": "11-09-21 19:58 Hi @mention{0} and @mention{1} ",
"Mention": [
{
"email": "emailuser1@mytenant.onmicrosoft.com",
"id": 10,
"loginName": "i:0#.f|membership|damien@abdndamodev.onmicrosoft.com",
"name": "DamoBird365"
},
{
"email": "emailuser2@mytenant.onmicrosoft.com",
"id": 17,
"loginName": "i:0#.f|membership|daveazure@abdndamodev.onmicrosoft.com",
"name": "Dave Azure"
}
]
}
Then using an apply to each on the body of the select action above, I work through each comment and re-purpose / re-build the mentions array as follows.
First, the Mentions Select Action is a select of the mention array for each comment and brings together an array of all names mentioned.
FROM: items('Apply_to_each')?['mention']
MAP: item()?['name']
The Split on Mention action is a compose using the split expression. Here I replace “@mentions{″ with an empty string ” and “}” with the string ‘ ||||SPLITME||||’. I then split on the new string with ‘SPLITME||||’ to give me a new array of objects that include an integer for the object and ||||. I’ve assumed that no one will ever comment with |||| or the phrase SPLITME. Please note that the last element is empty, but equally, it may contain the end of the comment which we need to capture later.
split(replace(replace(items('Apply_to_each')?['text'],'@mention{',''),'}','||||SPLITME||||'),'SPLITME||||')
The Select Text and Replace Mention Action creates an array that is based on a range of integers from 0 to the length of the mentions array. This allows us to re-purpose the objects based on an integer index. The map takes each object from the split above and replaces the integer and |||| with the name from the Mentions Select array created previously. Note that I use the integer index i.e. item() to get the correct name from the Mentions array.
FROM: range(0,length(item()?['mention']))
MAP: replace(outputs('Split_on_Mention_Compose')?[item()],concat(item(),'||||'),body('Mentions_Select')?[item()])
The final action Join Objects is a compose with an IF statement. If the length of the mentions array is 0 (i.e. no mentions for that comment), simply return the comment text. Else we join the above array with an empty string but we also concatenate the last element which in this case is empty. When we split the string on mentions, the last element will contain any text after the last mention and so we have to append it back on using concat.
if(equals(length(item()?['mention']),0),items('Apply_to_each')?['text'],concat(join(body('Select_Text_and_Replace_Mention'),''),last(outputs('Split_on_Mention_Compose'))))
Our last action is outside of the apply to each and it brings together the results of the Join Objects array. This uses the trick with the outputs() expression, which will bring all of the compose results from the apply to each, into a single array.
The result of the final compose action is an array of all of the comments, it includes the date and time the comment was posted and each of the mentions.
Pretty difficult. Is there an easier way? Please reach out and let me know if you think it can be done using a cleaner or more efficient means. How are you using this solution for your own flow?
Hi, let me ask one question regarding this great solution. If I add comments to SharePoint line item, can I also set the name of commenter in the body of http request? I am asking this because there is always “you commented” in SharePoint line but I should set the names for the approvers and this is coming from power automate approval flow.
Hi Balazs, missed your comment, apologies. The http action runs as a user. You would have to get the action logged on as the user in order to post by them.
There’s a error get when use output of select to input of apply to each.
error message shown – The ‘from’ property value in the ‘select’ action inputs is of type ‘Null’. The value must be an array.
And the first select action the expressions are invalid.can you share the expressions.
You’ll need to check the history of your flow. The input to that select action is empty / null. Maybe a typo? Or your source of data is empty.
I have same problem. HTTP Request returns data, but select says:
The ‘from’ property value in the ‘select’ action inputs is of type ‘Null’. The value must be an array.
There is no typos os sth 🙁
FROM should be:
body(‘Send_an_HTTP_request_to_SharePoint’)?[‘d’]?[‘results’]
so, remove the ?[‘body’] from it.
thanks for the fix Mark, I had the same issue. And many thanks to you Damien, your blog and videos which are fantastic. Thank you for sharing your wisdom!
In the apply to each, mentions select , what is mention and name? are these two variables ? please explain.
Amazingly detailed directions, THANK YOU! I also wanted to capture the Author so in the Select I added “Author” and the expression: “item()?[‘author’]?[‘name’]”
In the “apply to each” part of the flow, I added a compose to pull the name string using this expression: “items(‘Apply_to_each’)?[‘author’]”
Then in the Join, I added a reference to this Compose in the concat part of the expression so the author’s name was in parentheses after each comment.
The whole thing works beautifully. Thank you for saving me hours of time trying to figure all of this out!
Thank you for your kind post. Very much appreciated.
Hi dude, I am almost done with the flow.
The only thing missing is the author name as you did.
I have added the “author” in the expression and the compose in apply to each.
The missing thing that I dont get is how am I supposed the add the reference of the compose in the concat part in the Join to have author name in parentheses exactly like you did ?
I beg you mate lol
Thank you very much in advance !
Hi, two questions please :
– Where di you put the compose in the ‘apply to each”
– In the join, how did you added a reference to this compose in the concat part of the expression ?
Thanks a lor for your help.
Best Regards,
This is great! I am thinking there’s a more efficient way to do this though. Split it into an array like you did but then use xpath to join to mentions array directly. I’m trying to figure it out.
Hey,
This is great. The issue I have is that it collects the comments of all ID’s and it doesn’t update the right comments per ID.
Would you have any tips for this?
Having trouble with using the HTTP action on a document library. The comments do not display in the version history, but the http action shows succeeded. Would I need to use check-in/out maybe?
Any way you can post the entire flow
If I could buy you a beer I would!
I keep getting his error at the ‘Mention Select’ stage:
BadRequest
The ‘from’ property value in the ‘select’ action inputs is of type ‘String’. The value must be an array.
Anyone else having the same problem?
Thanks for an example how to handle comments with power automate, but I believe you have quite overcomplicated things with splitting, joining, introducing ‘SPLITME||||’ string, etc.
I have came with easier way how to replace mention tags and want to share it with community as a gratitude for initial example from DamoBird365:
1. Initialize varComments variable as an array
2. Initialize varComment variable as a string
3. For each Comment – outputs(‘Select_Comments’)[‘body’]
3.1. Set varComment variable to item()?[‘text’]
3.2. For each Mention – range(0,length(item()?[‘mention’]))
3.2.1. Compose varComment with replace(variables(‘varComment’),concat(‘@mention{’,item(),’}’),items(‘For_each_Comment’)?[‘mention’]?[item()]?[‘name’])
3.2.2. Set varComment variable to outputs(‘Compose_varComment’)
3.3. Compose array for varComments variable with
{
“Name”: @{items(‘For_each_Comment’)?[‘name’]},
“Date”: @{items(‘For_each_Comment’)?[‘date’]},
“Comment”: @{variables(‘varComment’)}
}
3.4. Append outputs(‘Compose_Array_for_varComments’) to varComments variable
4. Use varComments array as you please (create HTML table, etc.)
Thanks for sharing. It’s certainly another method. How does it compare for api calls as you’ll have nested apply to each? Also how do you handle {0} in the mention? It’s great to see alternatives, I also wonder if xpath would enable it to be done quicker and cleaner?
I have put more detail method description here: https://powerusers.microsoft.com/t5/image/serverpage/image-id/887816iF0114EA8559F088E/is-moderation-mode/true/image-dimensions/2500
“@mention& # 1 2 3 ;″ and “& # 1 2 5 ;” is handled with this compose fx: replace(variables(‘varComment’),concat(‘@mention & # 1 2 3 ;’,item(),’& # 1 2 5 ;’),items(‘For_each_Comment’)?[‘mention’]?[item()]?[‘name’])
It just needs to be run for each mention{x} to be replaced with relevant username.
It’s not the most optimal algorithm as it needs to go through all x=0,1,2… and it might become resource/time consuming in case of hundred mentions in the comment, but at least it avoids joining/splitting workarounds and clarifies algorithm.
Hi,
Our last action is outside of the apply to each and it brings together the results of the Join Objects array. This uses the trick with the outputs() expression, which will bring all of the compose results from the apply to each, into a single array.
If i place the compose outside for each loop, it will not recognize the Outputs(Join_Objects) as you have shown. Please give the complete flow description or suggest me to get all the comments into a sing array.
You’ll have missed the ‘ ‘ or your compose name isn’t join_objects but outputs(‘join objects’)
Hi Damobird365, i have followed exactly the steps you have shown till the Join Objects which is inside the Apply to each loop. But where to place the last compose action to get all the comments in a single array as you have shown in the last step. Can you please provide a screenshot of your flow in my mail id. its bit urgent.
Thanks in advance.