Workflow Loop Issue

I have created a test workflow to print loop input. So, that i can use later.

Can anyone suggest me what is wrong with loop input?

Please find workflow json attached too.

{
	"name": "Iterate Identities and Send Email",
	"description": "Workflow to iterate through identities using a loop and send an email for each identity",
	"modified": "2026-07-08T12:06:14.354944323Z",
	"modifiedBy": {
		"type": "IDENTITY",
		"id": "a244f1b207a44c858c838ccbf56a2332",
		"name": "6099714"
	},
	"definition": {
		"start": "Get List of Identities",
		"steps": {
			"End Step - Success": {
				"actionId": "sp:operator-success",
				"displayName": "End Step - Success",
				"type": "success"
			},
			"Get List of Identities": {
				"actionId": "sp:get-identities",
				"attributes": {
					"inputQuery": "6099123456",
					"searchBy": "searchQuery"
				},
				"displayName": "Get List of Identities",
				"nextStep": "Loop Through Identities",
				"type": "action",
				"versionNumber": 2
			},
			"Loop Through Identities": {
				"actionId": "sp:loop:iterator",
				"attributes": {
					"input.$": "$.getListOfIdentities.identities",
					"loopInput.$": "$.getListOfIdentities",
					"start": "Send Email to Identity",
					"steps": {
						"End Loop Step": {
							"actionId": "sp:operator-success",
							"displayName": "End Loop Step",
							"type": "success"
						},
						"Send Email to Identity": {
							"actionId": "sp:send-email",
							"attributes": {
								"body": "<p>Hello ${{loop.loopInput.displayName}}, this is an automated email notification.</p>\n<p>{{$.loopThroughIdentities.loopInput}}</p>\n<p>{{loop.loopInput}}</p>",
								"context": {
									"displayName.$": "$.loop.loopInput.displayName"
								},
								"from": "no-reply@sailpoint.com",
								"recipientEmailList": [
									"amrit.raj@lseg.com"
								],
								"replyTo": null,
								"subject": "Notification for Identity"
							},
							"displayName": "Send Email to Identity",
							"nextStep": "End Loop Step",
							"type": "action",
							"versionNumber": 2
						}
					}
				},
				"displayName": "Loop Through Identities",
				"nextStep": "End Step - Success",
				"type": "action",
				"versionNumber": 1
			}
		}
	},
	"creator": {
		"type": "IDENTITY",
		"id": "a244f1b207a44c858c838ccbf56a2332",
		"name": "6099123456"
	},
	"trigger": {
		"type": "EXTERNAL",
		"attributes": {
			"id": "idn:external-http",
			"integrationId": null
		}
	}
}

This is what i have received as email.

Hello ${{loop.loopInput.displayName}}, this is an automated email notification.

{{$.loopThroughIdentities.loopInput}}

{{loop.loopInput}}

${{loop.loopInput.displayName}} it should be {{$.loop.loopInput.displayName}}

Hello Amrit, you have a couple of small syntax issues in the email body that are causing the expressions to print as raw text instead of resolving.

1. The email body syntax: Your Send Email step already has the context mapping set up correctly:

"context": {
  "displayName.$": "$.loop.loopInput.displayName"
}

So in the email body, just reference it as ${displayName}:

<p>Hello ${displayName}, this is an automated email notification.</p>

Right now your body has ${{loop.loopInput.displayName}} which mixes two different syntax styles, that’s why it prints raw instead of resolving. The Send Email action uses its Templating Context to resolve variables, so map what you need in the context and then use ${variableName} in the body. That’s the documented approach.

2. Step reference inside the loop: This part also won’t resolve:

{{$.loopThroughIdentities.loopInput}}

Inside a loop, the current item is always accessed through $.loop.loopInput, not through the step name. So for your Send Email context mappings, use $.loop.loopInput.displayName, $.loop.loopInput.name, $.loop.loopInput.id, etc.

3. The loopInput.$ in your loop config: I would remove this line from your loop attributes:

"loopInput.$": "$.getListOfIdentities"

Your loop input array is already defined here:

"input.$": "$.getListOfIdentities.identities"

That’s the array the loop iterates through, and each identity from it automatically becomes available as $.loop.loopInput. The extra loopInput.$ line is not needed and can make the config confusing. Official reference.

Thanks, @punna0001 I have fixed that.
I have printed loop input in email and output is like below

Then I have checked step output of loop where I can see loopinput as _index, _type and type. Then how will I get identity name or id or other identity attribute?

"loop":{
"context": null ,
"loopInput":{
"_index": "61" ,
"_type": "identity" ,
"type": "identity"
}
}

Your loop input is only carrying reference metadata right now. So there is no displayName, name, email, or id available at that point. First, open the output of Get List of Identities and check what’s actually inside $.getListOfIdentities.identities. For the loop to use identity fields directly, each item in that array must contain those fields, especially id.

If id is available, add a Get Identity step inside the loop before Send Email and pass the current identity id: $.loop.loopInput.id .Your loop should be:

Get List of Identities
→ Loop Through Identities
   → Get Identity (input: $.loop.loopInput.id)
   → Send Email
   → End Loop Step

Then in the Send Email context, pull values from the Get Identity output:

{
  "displayName.$": "$.getIdentity.name",
  "identityId.$": "$.getIdentity.id",
  "email.$": "$.getIdentity.emailAddress"
}

And in the body:

<p>Hello ${displayName},</p>
<p>Identity ID: ${identityId}</p>
<p>Email: ${email}</p>

Also keep the loop input simple: "input.$": "$.getListOfIdentities.identities"

Remove the extra loopInput.$ line if it’s still there.

The main thing is $.loop.loopInput only contains whatever item the loop is iterating over. If that item only has _index, _type, and type, there’s no identity data to use in the email. Get the identity id into the loop first, then use Get Identity to fetch the full details.

tried but it fails

You have got the Get Identity input set correctly to $.loop.loopInput.id, so that part is good. The reason it might still be failing is because the loop item doesn’t actually contain an id field. We already saw it only has _index, _type, and type.

The fix is upstream in your Get List of Identities step. Open its Step Output and check what fields each identity item actually contains. If id is missing, add it in the Additional Output Data section of that step’s configuration.

Once id is included in the Get List output, the loop items will carry it, and $.loop.loopInput.id will resolve properly into Get Identity.

Can you share the Step Output of Get List of Identities so we can see exactly what’s coming back?

Hi @Amrit1897 ,

Please find attached below workflow Json, it exactly met your requirement.

{

    "name": "Iterate Identities and Send Email",

    "description": "Workflow to iterate through identities using a loop and send an email for each identity",

    "modified": "2026-07-09T09:26:50.005866947Z",

    "modifiedBy": {

        "type": "IDENTITY",

        "id": "a244f1b207a44c858c838ccbf56a2332",

        "name": "6099123456"

    },

    "definition": {

        "start": "Get List of Identities",

        "steps": {

            "End Step - Success": {

                "actionId": "sp:operator-success",

                "displayName": "",

                "type": "success"

            },

            "Get List of Identities": {

                "actionId": "sp:get-identities",

                "attributes": {

                    "inputQuery": "bgogu",

                    "searchBy": "searchQuery"

                },

                "displayName": "",

                "nextStep": "Loop",

                "type": "action",

                "versionNumber": 2

            },

            "Loop": {

                "actionId": "sp:loop:iterator",

                "attributes": {

                    "input.$": "$.getListOfIdentities.identities",

                    "start": "Get Identity",

                    "steps": {

                        "End Step - Success 1": {

                            "actionId": "sp:operator-success",

                            "displayName": "",

                            "type": "success"

                        },

                        "Get Identity": {

                            "actionId": "sp:get-identity",

                            "attributes": {

                                "id.$": "$.loop.loopInput.id"

                            },

                            "displayName": "",

                            "nextStep": "Send Email",

                            "type": "action",

                            "versionNumber": 2

                        },

                        "Send Email": {

                            "actionId": "sp:send-email",

                            "attributes": {

                                "body": "<p>Hi {{$.getIdentity.attributes.displayName}},<br><br>Please contatct your manager to get access.

                                <br><br>

                                name: {{$.getIdentity.attributes.name}} <br><br> IdentityId: {{$.getIdentity.attributes.id}}

                                <br><br>Thanks,<br>IAM Team.</p>",

                                "context": {},

                                "recipientEmailList.$": "$.getIdentity.attributes.email",

                                "subject": "Your identity created"

                            },

                            "displayName": "Send Email to identity",

                            "nextStep": "End Step - Success 1",

                            "type": "action",

                            "versionNumber": 2

                        }

                    }

                },

                "displayName": "",

                "nextStep": "End Step - Success",

                "type": "action",

                "versionNumber": 1

            }

        }

    },

    "creator": {

        "type": "IDENTITY",

        "id": "a244f1b207a44c858c838ccbf56a2332",

        "name": "6099123456"

    },

    "trigger": {

        "type": "EXTERNAL",

        "attributes": {

            "id": "idn:external-http",

            "integrationId": null

        }

    }

}

If you need to add more identity attribute details in the email body add like below:

Thanks.

Thanks @Bapu-Gogu , It is working now and now I am able to loop through the identities.

Now I am facing some issue in comparing the access.

$.getAccess.accessItems[*].id is an array it is not getting compared in compare string operator.
If I replace * with number it works as it becomes a string $.getAccess.accessItems[0].id.

But I want $.getAccess.accessItems[*].id to work and it gets compared. Basically $.getAccess.accessItems[*].id Contains an entitlement Id.

So, need help on Compare String operator, which is not working currently.

Hello Amrit, good, the loop part is fixed now.

For this issue, $.getAccess.accessItems[*].id returns an array of IDs, not one string. That’s why Compare Strings isn’t working. It expects one value against one value. When you use $.getAccess.accessItems[0].id, it works because that resolves to a single string.

For checking whether a specific entitlement exists in the list, use Verify Data Type instead of Compare Strings. Try this in the value field:

$.getAccess.accessItems[?(@.id == '6a2cbbf2a2a383fbxxxxxxxxxxxx5ad')].id

Set Data Type to Exists. So the logic becomes:

Get Access
→ Verify Data Type
   Value: $.getAccess.accessItems[?(@.id == 'ENTITLEMENT_ID_HERE')].id
   Data Type: Exists
   → True: Assign User Levels
   → False: Send Email / End Step

Just make sure the ID you are comparing is the same ID type that is coming in accessItems[*].id. The id field in Get Access output is the SailPoint access item ID, not the entitlement name or native value. Open the Get Access Step Output and confirm what id actually contains before putting it in the filter.

If the filtered JSONPath doesn’t work in your tenant, the fallback is to loop through $.getAccess.accessItems and compare each $.loop.loopInput.id individually against your entitlement ID using Compare Strings.

Main point: [*].id gives multiple values, so Compare Strings is not the right operator here. For an existence check, filtered JSONPath + Verify Data Type is cleaner.

Can you put your requirement here.

If you want to compare an access with all the accesses in tenant is not right thing after entering into loop, it will give you list access items, in ISC current loop capability, it is not possible to put loop inside loop.

What exactly you want to print from loopInput that you have to metioned like loopInput.name etc.