Groups: a shared roster with real membership and data infrastructure of its own
Global Chat: a group's channel is the exact same getChannelId/channelConnect flow, just with channel type "gr" instead of "gl"; Friends & Presence for the presence half of this same reference app; Entity Storage for Group Entity, the group-scoped data store this article names but doesn't demonstrate.
brainCloud's Group service is more than a roster with Chat and Presence layered on top. Membership is real infrastructure in its own right: open and closed groups, invitations, join requests with approve/reject, and per-member roles and attributes. A group also carries its own data, separate from anything shared with a single member: Group Data, one JSON blob per group, and Group Entities, a full paginated, queryable collection scoped to the group, both with their own ACLs. This article covers what bcchat, the reference app here, actually drives: create, join, leave, plus Chat and Presence layered on the roster once a groupId exists, which is genuinely where those two services' patterns reuse directly. Group Data, Group Entities, invitations, join requests, and roles are the rest of the service: real and documented, just not part of this particular walkthrough.
How it works
- List the groups you're already in, once at login. getMyGroups() returns every group the player belongs to.
- Loading a group is a three-step chain, reusing Chat's exact pattern. Resolve the group's channel id via getChannelId("gr", groupId, ...), connect to it the same way as Global Chat's "gl" channel, then layer group-specific membership and presence on top.
- Membership and presence are separate calls from the channel connect. readGroupMembers(groupId) gets the roster; registerListenersForGroup(groupId, true) gets live online/offline status for that roster, including each member's initial state in the response.
- Creating a group is one call with a full config payload: name, type, visibility, ACL, and default attribute blobs for owner and members. A successful call produces a normal group, loaded the same way as any other.
Code
fetchGroups()
{
this.bcWrapper.group.getMyGroups(result =>
{
if (result.status === 200)
{
result.data.groups.forEach(group => this.loadGroup(group));
}
});
}loadGroup(group)
{
this.bcWrapper.chat.getChannelId("gr", group.groupId, result =>
{
group.id = result.data.channelId;
this.connectToChannel(group, () =>
{
this.bcWrapper.group.readGroupMembers(group.groupId, result =>
{
group.members = Object.keys(result.data).map(profileId => ({
id: profileId,
name: result.data[profileId].playerName,
pic: result.data[profileId].pic,
online: false,
}));
this.bcWrapper.presence.registerListenersForGroup(group.groupId, true, result =>
{
// result.data.presence[] carries each member's CURRENT online status:
// this is the initial snapshot; live changes arrive via the RTT presence callback
result.data.presence.forEach(presence => { /* merge into group.members */ });
});
});
});
});
}this.bcWrapper.group.createGroup(
name,
"bcchat", // groupType: an app-defined string, not a fixed enum
true, // isOpenGroup
null, // acl
{}, // ownerAttributes
{}, // defaultMemberAttributes
{}, // data
result =>
{
if (result.status === 200) this.loadGroup(result.data);
});this.bcWrapper.group.joinGroup(group.groupId, result =>
{
if (result.status === 200) this.loadGroup(group);
});
this.bcWrapper.group.leaveGroup(group.groupId, result => { /* ... */ });Gotchas worth knowing up front
- Groups have a real member cap, not an unlimited roster. Max Size is a per-group-type portal setting, 2 to 50 members, set under Design → Groups → Group Types. The platform-wide default ceiling is also 50, specifically to limit optimistic-locking contention on group and group entity writes; raising it past 50 is a support request, not a client-side flag.
- A group's chat channel, membership, and presence are three independent calls. No single call returns all three. Skipping any one of them (channel connect, readGroupMembers, registerListenersForGroup) leaves a group missing chat, a roster, or live status, respectively.
- readGroupMembers's response is keyed by profile id, which is a feature, not friction. Looking up one specific member's data is a direct result.data[profileId], no scan required. Iterating the whole roster is still one line: Object.keys(result.data).
- registerListenersForGroup's response carries each member's presence at registration time. That's separate from the live RTT push that follows: it's the initial snapshot, not a live update. The relationship mirrors channelConnect's message history to the live chat push (see the Global Chat article).
- groupType is an app-defined string ("bcchat" here). The SDK doesn't fix it to an enum. It's typically used server-side to distinguish group kinds (guild vs. party vs. clan) when a game has more than one.