Combine members from two groups into a single array and trigger approvals
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 hours ago
Hi everyone,
I have a requirement where I’m working with two groups in ServiceNow.
I’m able to:
- Look up both group records
- Retrieve the members from each group
What I’m trying to do next is:
- Combine the users who are common to both groups (intersection of Group A and Group B) into a single array
- Use that array to trigger an approval for those users
I’m currently stuck on:
- How to properly use the forEach loop
- How to set and populate a variable/array with the matching users
Could someone please help with an example or guidance on how to:
- Compare members from two groups
- Store the common users in an array
- Trigger approvals for those users
Any help would be appreciated. Thanks in advance!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 hours ago
Hello Saint,
Here is a complete working example with explanation at each step:
Step 1 — Retrieve Members from Both Groups
function getGroupMembers(groupSysId) {
var members = [];
var gr = new GlideRecord('sys_user_grmember');
gr.addQuery('group', groupSysId);
gr.query();
while (gr.next()) {
members.push(gr.getValue('user')); // stores user sys_id
}
return members;
}
var groupASysId = 'YOUR_GROUP_A_SYS_ID';
var groupBSysId = 'YOUR_GROUP_B_SYS_ID';
var groupAMembers = getGroupMembers(groupASysId);
var groupBMembers = getGroupMembers(groupBSysId);
gs.info('Group A members: ' + groupAMembers);
gs.info('Group B members: ' + groupBMembers);
Step 2 — Find the Intersection (Common Users)
This is where your forEach confusion likely is. Here is the pattern explained clearly:
var commonUsers = [];
// Loop through every user in Group A
groupAMembers.forEach(function(userSysId) {
// For each Group A user, check if they also exist in Group B
if (groupBMembers.indexOf(userSysId) !== -1) {
// indexOf returns -1 if NOT found, anything else means it IS found
commonUsers.push(userSysId);
}
});
gs.info('Common users: ' + commonUsers);
gs.info('Total common users: ' + commonUsers.length);
Why indexOf !== -1?
indexOfsearches the array for a value- Returns the position (0, 1, 2...) if found
- Returns -1 if not found
- So
!== -1means "was found in the array"
Step 3 — Trigger Approvals for Common Users
function createApprovalForUser(userSysId, targetRecord) {
var approval = new GlideRecord('sysapproval_approver');
approval.setValue('approver', userSysId);
approval.setValue('sysapproval', targetRecord); // sys_id of record needing approval
approval.setValue('state', 'requested');
approval.setValue('source_table', 'YOUR_TABLE_NAME'); // e.g. 'sc_request'
var approvalSysId = approval.insert();
gs.info('Approval created: ' + approvalSysId + ' for user: ' + userSysId);
return approvalSysId;
}
// Loop through common users and create an approval for each
commonUsers.forEach(function(userSysId) {
createApprovalForUser(userSysId, current.sys_id);
});
Complete Combined Script
// ------------------------------------------------
// 1. Helper — get all member sys_ids from a group
// ------------------------------------------------
function getGroupMembers(groupSysId) {
var members = [];
var gr = new GlideRecord('sys_user_grmember');
gr.addQuery('group', groupSysId);
gr.query();
while (gr.next()) {
members.push(gr.getValue('user'));
}
return members;
}
// ------------------------------------------------
// 2. Helper — create one approval record
// ------------------------------------------------
function createApproval(userSysId, recordSysId, tableName) {
var approval = new GlideRecord('sysapproval_approver');
approval.setValue('approver', userSysId);
approval.setValue('sysapproval', recordSysId);
approval.setValue('state', 'requested');
approval.setValue('source_table', tableName);
var newSysId = approval.insert();
// Log the user's name alongside the sys_id for easier debugging
var user = new GlideRecord('sys_user');
user.get(userSysId);
gs.info('Approval created for: ' + user.getDisplayValue() + ' (' + userSysId + ')');
return newSysId;
}
// ------------------------------------------------
// 3. Main logic
// ------------------------------------------------
var GROUP_A_SYS_ID = 'YOUR_GROUP_A_SYS_ID'; // replace with real sys_ids
var GROUP_B_SYS_ID = 'YOUR_GROUP_B_SYS_ID';
var TARGET_TABLE = 'YOUR_TABLE_NAME'; // table of the record being approved
var groupAMembers = getGroupMembers(GROUP_A_SYS_ID);
var groupBMembers = getGroupMembers(GROUP_B_SYS_ID);
// ------------------------------------------------
// 4. Find intersection
// ------------------------------------------------
var commonUsers = [];
groupAMembers.forEach(function(userSysId) {
if (groupBMembers.indexOf(userSysId) !== -1) {
commonUsers.push(userSysId);
}
});
gs.info('Found ' + commonUsers.length + ' common user(s): ' + commonUsers.join(', '));
// ------------------------------------------------
// 5. Create approvals
// ------------------------------------------------
if (commonUsers.length === 0) {
gs.warn('No common users found between the two groups. No approvals created.');
} else {
commonUsers.forEach(function(userSysId) {
createApproval(userSysId, current.sys_id, TARGET_TABLE);
});
}
Visual Breakdown of the forEach Logic
groupAMembers = ['user1', 'user2', 'user3', 'user4']
groupBMembers = ['user2', 'user4', 'user5']
forEach iteration:
'user1' → indexOf in B = -1 → NOT added ✗
'user2' → indexOf in B = 0 → ADDED ✓
'user3' → indexOf in B = -1 → NOT added ✗
'user4' → indexOf in B = 1 → ADDED ✓
commonUsers = ['user2', 'user4']
Key Things to Remember
| Concept | Detail |
|---|---|
sys_user_grmember |
The table that links users to groups |
indexOf !== -1 |
The standard way to check if a value exists in a JS array |
forEach |
Loops every item — the inner function receives each item as its argument |
sysapproval_approver |
The table where individual approval records live |
Always guard commonUsers.length === 0 |
Avoid silently creating no approvals without a log entry |
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 hours ago
Hi @Pavan Srivastav Thank you for your respose, I'm trying to build this in flow designer, and wondering how to set the flow variable values as array of unique user records, with common members of both the groups
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2 hours ago
Sure Saint.
Flow Designer handles this differently from script since it's a no-code/low-code environment, but for array intersection logic you will need a small Script step. Here is the complete approach:
Overall Flow Structure
Trigger (your trigger)
↓
Look Up Records → Group A Members (sys_user_grmember, group = Group A)
↓
Look Up Records → Group B Members (sys_user_grmember, group = Group B)
↓
Script Step → Find Common Users + Build Array
↓
For Each → Loop commonUsers array
↓
Ask For Approval (inside loop)
Step 1 — Create Flow Variables
Before building the steps, set up your flow variables first. Go to Flow Variables (the pill icon at the top of the flow canvas):
| Variable Name | Type | Notes |
|---|---|---|
group_a_sys_id |
String | sys_id of Group A |
group_b_sys_id |
String | sys_id of Group B |
common_users |
Array.Reference (sys_user) | Will hold the intersection result |
Step 2 — Look Up Group Members
Add two Look Up Records actions:
Look Up #1 — Group A Members
Table: Group Members [sys_user_grmember]
Condition: Group is <your Group A sys_id or trigger pill>
Look Up #2 — Group B Members
Table: Group Members [sys_user_grmember]
Condition: Group is <your Group B sys_id or trigger pill>
These return record collections you will reference in the Script step.
Step 3 — Script Step (Core Logic)
Add an Action → Script step. This is where the intersection happens.
Script Step Inputs
Define these inputs inside the Script step so you can pass Flow data in:
| Input Name | Value (drag pill from) |
|---|---|
groupAMembers |
Look Up #1 → Records |
groupBMembers |
Look Up #2 → Records |
Script Step Outputs
Define one output:
| Output Name | Type |
|---|---|
commonUsers |
Array |
The Script
(function execute(inputs, outputs) {
// Build array of user sys_ids from Group A
var groupAUserIds = [];
var membersA = inputs.groupAMembers;
for (var i = 0; i < membersA.size(); i++) {
var memberA = membersA.get(i);
groupAUserIds.push(memberA.getValue('user'));
}
// Build array of user sys_ids from Group B
var groupBUserIds = [];
var membersB = inputs.groupBMembers;
for (var j = 0; j < membersB.size(); j++) {
var memberB = membersB.get(j);
groupBUserIds.push(memberB.getValue('user'));
}
// Find intersection — users in BOTH groups
var commonUserIds = [];
groupAUserIds.forEach(function(userSysId) {
if (groupBUserIds.indexOf(userSysId) !== -1) {
// Deduplicate — only add if not already in array
if (commonUserIds.indexOf(userSysId) === -1) {
commonUserIds.push(userSysId);
}
}
});
gs.info('Flow: Common users found = ' + commonUserIds.length);
// Set the output — array of user sys_ids
outputs.commonUsers = commonUserIds;
})(inputs, outputs);
Step 4 — Set Flow Variable
After the Script step, add a Set Flow Variables action:
common_users = Script Step → commonUsers (output pill)
This writes the intersection result into your flow-level variable so it is available to subsequent steps.
Step 5 — For Each Loop → Ask For Approval
Add a For Each action:
Items: Flow Variables → common_users
Inside the loop, add Ask For Approval:
Table: your target table
Record: your trigger record pill
Approve: For Each Item → User (the current loop item)
Full Flow Visualised
[Trigger]
│
├─► [Look Up Records] Group A members ──► groupARecords
│
├─► [Look Up Records] Group B members ──► groupBRecords
│
├─► [Script Step]
│ inputs: groupARecords, groupBRecords
│ output: commonUsers (array of sys_ids)
│
├─► [Set Flow Variables]
│ common_users = Script output pill
│
└─► [For Each] items = common_users
│
└─► [Ask For Approval]
approver = current loop item (user)
Important Gotchas
Gotcha 1 — Array type must match When you define common_users as a flow variable, set the type to Array.Reference pointing to sys_user — this ensures the For Each loop correctly resolves each item as a user record for the approval step.
Gotcha 2 — Script step input type for collections When passing Look Up Records output into a Script step input, set the input type to GlideRecord List not String — otherwise .size() and .get() won't work on it.
Gotcha 3 — Empty intersection guard Add an If condition after the Set Flow Variables step before the For Each:
Condition: common_users is not empty
This prevents the For Each from erroring on an empty array if the two groups share no members.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
22m ago
for 3rd flow variable i only see one option - and not this below
common_users Array.Reference (sys_user)
and in my custom action its the same. could you help me figure this thing out.\
