function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion

Determine the Public Groups a User Is In

I am working on something where I want to allow an admin to assign items to a Public Group.  Then, at runtime, I need to determine what Public Groups a user is in (directly or via Roles / Roles + Subordinates) and display the appropriate items.  Because Public Groups can be complicated having other Public Groups as Members and also Roles & Subordinates, figuring out the Groups a user is in can be difficult.


I started a conversation on Twitter about it and then did some research and have a starting point.  I'd like to encourage others to help me vet this and then it could become a Cookbook item down the line.  Here's what I got...


I think this can be done in 2 steps without much looping.  From what I can tell, the Group table can contain all kinds of Groups ("Regular" Public Groups, Roles, Soles & Subordinates, Queues, etc.).  The Group Member table seems to only contain pointers to Users or to Other Groups.  Groups that are copies of Roles and Roles & Subordinates have a Related Id filled in.  


I believe the following 2 queries could determine the Public Groups that a User is actually in...


1) Query the Group table to get the Group records where the Related Id = the users's role.  We could filter for Type of Role or RoleAndSubordinates, but it seems that anything with a RelatedId filled in is one of those types.  This query is essentially getting the Group records that mirror our Role record.



List<String> roleRelatedGroupIds = new List<String>();
for (Group g : [SELECT id, RelatedId, Type FROM Group where RelatedId= :UserInfo.getUserRoleId()]{


2) Next we query the Group Member table for records where our user is specifically assigned and  also include records where our role is assigned.


List<String> allGroupIDs = new List<String>();
for (GroupMember gm : [SELECT Id,,, group.type FROM GroupMember where (UserOrGroupId = :UserInfo.getUserId() AND group.type='Regular') OR (UserOrGroupId IN :roleRelatedGroupIds AND group.type='Regular')]{



I'd need to hack at this given lots of users and lots of Group Types like Queues, Territories, Roles, Public Groups, etc.  From what I've been able to test, this works.


Grrr.  This works unless you start layering Groups in Groups in Groups


i use this for groups in groups in groups.



public class groupMemberObj{
	public User user {get; set;}
	public Group public_group {get; set;}
	public UserRole role {get; set;}
	public string reason {get; set;}

public static list<groupMemberObj> groupMembers;

public static list<groupMemberObj> getGroupMembers(list<Group> groups)
	if(groupMembers==null) groupMembers = new list<groupMemberObj>();
		Group[] sub_groups = new list<Group>();
		id[] group_member_ids = new list<id>();
		//map<id,id> user_ids = new map<id,id>();
		id[] user_ids = new list<id>();
		// map userOrGroup to Group Name
		map<id,Group> groupIdToGroup_map = new map<id,Group>();
		// map the group member to the group
		map<id,id> groupMemberIdToGroupId_map = new map<id,id>();
			iterate through each sub-group level where the group member
			is another group until we have all users from all group memberships
			of all groups and there are no more sub-groups as group members.
			// if this list is not cleared each time the list of ids to query
			// keeps growing and eventualy you hit max SQL queries error

			for(Group g : groups)
				// to get information about the group later, e.g. the group name for a user
				for(GroupMember gm : g.groupmembers)

			// keep trying to get groups from GroupMember.userOrGroupId
			sub_groups = [Select g.Type, g.RelatedId, g.Name, g.Id, (Select Id, GroupId, UserOrGroupId From GroupMembers) From Group g where Id In :group_member_ids];
			// if group members are groups get those groups members
				// clear the list of groups and rebuild because we only want these newly found groups each time
				// from group members of groups
				for(Group g : sub_groups){
			// keep trying to get users from GroupMember.userOrGroupId.
			// skip any users already added to the list
			User[] users = [select Id,Email,Name from User where (Id In :group_member_ids) and (Id Not In :user_ids) and (isActive=true)];
			for(User u :users)
				// add users to the final list of group members
				groupMemberObj gmo = new groupMemberObj();
				gmo.public_group = groupIdToGroup_map.get(groupMemberIdToGroupId_map.get(;
				// prevent the user from being selected in the query more than once
		// keep getting group members for groups
		} while (sub_groups.size()>0);
	return groupMembers;




and then this to traverse the hierarchy for roles.



public static list<groupMemberObj> roleSubordinates;
public static list<groupMemberObj> getRoleSubordinates(list<UserRole> roles)
	if(roleSubordinates==null) roleSubordinates = new list<groupMemberObj>();
		// get the users that are in a role under the current role
		id[] role_ids = new list<id>();
		id[] user_ids = new list<id>();
		// map the roleid to the role
		map<id,UserRole> roleIdToRole_map = new map<id,UserRole>();
		// map userid to userroleid
		map<id,id> userIdToRoleId_map = new map<id,id>();

			for(UserRole r :roles)
				// to get information about the role later, e.g. the role name for the user

			// try to find subordinates
			list<UserRole> subordinate_roles = [select Id,Name from UserRole where (ParentRoleId In :role_ids) and (PortalRole=null)];
			// if there are subordinate roles
				// rebuild the list of roles
				for(UserRole r :subordinate_roles)
			// keep trying to get users
			list<User> subordinate_users = [select Id,Email,UserRoleId,Name from User where (UserRoleId In :role_ids) and (Id Not In :user_ids) and (isActive=true)];

				for(User u : subordinate_users)
					// add the user
					groupMemberObj gmo = new groupMemberObj();
					gmo.role = roleIdToRole_map.get(u.userRoleId);
					// prevent the user from being added more than once
		} while (roles.size()>0);
	return roleSubordinates;



Koustubh KulkarniKoustubh Kulkarni
can you provide test class for the same?
Richard Fiekowsky 3Richard Fiekowsky 3
@Koustubh Don't you also want him to run the test class for you and send you the logs?
@Walter TY for this code , I have much use for it.