Groups
Groups have a fixed membership and suit teams, parent groups, interest groups and the like. Users can create groups from the client, invite others, apply to join public groups, and join through join links. Owners and admins can edit group profiles, manage members and approve join requests.
The Flutter SDK stores "My groups" and any member lists it has read in the local database, and exposes them as live lists (LiveList). They sync automatically after sign-in, reconnects and returning to the foreground; changes made by other members or on other devices show up in real time, and nothing needs refetching after the app restarts. Data you only page through, such as join requests and invitations, is queried page by page.
This page covers operations on groups and their members. To send group messages, see Messages (use the target SendTarget.group(groupId)); for group files, see Files and images. Group concepts (types, roles, settings, status) are the same as on the server; for the full rules, see Group management in the server docs.
| You want to | See |
|---|---|
| Create groups and show "My groups" | Create a group, My groups |
| Change the group name, announcement or avatar | Edit group profile and settings |
| Show, invite and remove members | Member list, Invite members, Remove members |
| Set admins, mute, group blocklist | Set admins, Mute, Group blocklist |
| Let users find and apply to public groups, and approve applications | Public groups and join requests |
| Handle invitations sent to the user | Whether invitations need confirmation |
| Add people with a link or QR code | Join links |
| Leave, dissolve or transfer ownership | Leave and dissolve, Transfer ownership |
Basics
Group types
GroupType | Description |
|---|---|
private | Private group, the default. People join only when invited by a member, through a join link, or when added by your server. Non-members can't see the group, and any operation on it returns not_found |
public | Public group. Users can find it, see its name, avatar, description and member count, and apply to join |
Roles and permissions
| Action | Owner owner | Admin admin | Member member |
|---|---|---|---|
| Edit group profile and settings | Yes | Yes (except group type) | No |
| Invite others | Yes | Yes | Depends on the member_invite setting |
| Remove and mute members, manage the group blocklist | Yes | Regular members only | No |
| Mute everyone, approve join requests, create join links | Yes | Yes | No |
| Set admins, transfer ownership, dissolve the group | Yes | No | No |
| Change their own group nickname | Yes | Yes | Yes |
In Dart, roles are the enum GroupRole (owner, admin, member). A group can have up to 20 admins. The owner can't be removed, muted or added to the group blocklist. To leave, the owner must transfer ownership first.
Group settings
| Setting | Dart enum | Values | Description |
|---|---|---|---|
member_invite | MemberInvite | free (default), approval, disabled | Whether regular members can invite others: directly, with owner or admin approval, or not at all |
join_mode | JoinMode | approval (default), free | How applications to a public group work: approval required, or join on applying. null for private groups and can't be set |
mute_all | — | true, false | Mute everyone; only the owner and admins can speak |
max_members | — | Read-only | The group's member limit; only your server or the console can set it |
When reading group information these settings are strings (such as group.memberInvite == 'approval'); when changing them, pass the enums.
Create a group
A user who creates a group from the client becomes its owner and can invite initial members at the same time (up to 100). Initial members are handled like invitations: if someone's invitation setting requires confirmation, they get an invitation and join only after accepting.
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
final group = await im.groups.create(
name: '产品研发群',
type: GroupType.private,
description: '产品和研发日常沟通',
members: ['lisi', 'wangwu', 'zhaoliu'],
message: '拉你进项目群', // 给需要确认的被邀请人看的附言
);
for (final r in group.results) {
if (!r.ok) {
print('${r.username} 没有加入:${r.code}');
} else if (r.status == 'invited') {
print('${r.username} 需要确认后入群');
}
}- The return value is a
CreateGroupResult: all of the new group's information, plusresults, which lists each initial member's outcome in request order. Initial members failing to join doesn't stop the group from being created. - The new group appears in "My groups" right away.
- If the app turns off client group creation in its runtime policies, the call rejects with
permission_denied. Readim.config?.clientGroupCreateEnabledfirst to decide whether to show a "Create group" option. - If the user has joined the maximum number of groups, it rejects with
limit_exceeded(details['reason']isuser_group_limit) and no group is created. If the app has reached its plan's group limit, the reason isapp_group_limit. - The group name, description, announcement and avatar go through content moderation. If they fail, the call rejects with
content_rejectedanddetails['field']names the field.
Group creation is never retried automatically
After a network error or timeout, the SDK refreshes the user's group list. If it finds the new group (owned by the user and created after this call), it returns success with an empty results list; use members() to see who actually joined. If not, it rejects with timeout and the user decides whether to try again. On rate_limited, tell the user; don't retry automatically.
My groups
im.groups.list() returns a live list of every group the user has joined. Each item is a GroupView, sorted by group name. The SDK syncs it after sign-in; creating, joining, leaving, being removed, dissolution and group profile changes all show up in the list. Call dispose() when you no longer show it.
import 'package:deeprespond_im_flutter/deeprespond_im_flutter.dart';
import 'package:flutter/material.dart';
class MyGroupsPage extends StatefulWidget {
const MyGroupsPage({super.key, required this.im});
final DRClient im;
@override
State<MyGroupsPage> createState() => _MyGroupsPageState();
}
class _MyGroupsPageState extends State<MyGroupsPage> {
late final LiveList<GroupView> _groups = widget.im.groups.list();
@override
void dispose() {
_groups.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => LiveListBuilder<GroupView>(
list: _groups,
builder: (context, snapshot) => ListView(
children: [
for (final g in snapshot.items)
ListTile(
title: Text(g.group.name.isNotEmpty ? g.group.name : '群聊'),
trailing: Text('${g.group.memberCount}'),
),
],
),
);
}The user's role, group nickname and mute state are in group.self:
final view = await im.groups.get('1840012345678901');
final self = view?.group.self;
final canManage = self?.role == GroupRole.owner || self?.role == GroupRole.admin;im.groups.get(groupId)reads local storage first and returns the group if found. If it isn't stored locally (for example a public group someone shared) or you passrefresh: true, it queries the server. Groups the user can't see (nonexistent, or private groups they're not in) returnnull.- For groups the user has left,
inMyGroupsisfalseandgroupholds the last known information from before they left, so the conversation list can still show the group's name and avatar. - To query several groups at once, use
im.groups.lookup(groupIds)(up to 100). Public groups the user isn't in return only public fields, and groups the user can't see are left out.
Changes to group profiles and to "My groups" are reported with GroupsChanged: upserted lists groups added or changed, and removed lists groups that left "My groups":
im.on<GroupsChanged>().listen((e) {
print('变化的群:${e.upserted},离开的群:${e.removed}');
});Edit group profile and settings
The owner and admins can edit the group profile and settings; only the arguments passed are sent. Only the owner can change the group type. Online members receive the change and the local group information updates.
await im.groups.update(
'1840012345678901',
name: '产品研发群(2 期)',
announcement: '本周五发布 2.3 版本',
attributes: {'project_id': 'P-1024', 'old_key': null}, // 按项合并,null 删除这个键
memberInvite: MemberInvite.approval,
);- Changing a public group to private cancels all pending join applications. Changing a private group to public sets the application mode to approval required by default.
- Setting
joinModeon a private group rejects withinvalid_argument. - Custom attributes are visible to all members and merged key by key;
clearAttributes: trueclears them all. - If several people may edit at once, pass the
group.infoVersionyou read asversion. If the profile was changed by someone else, the call rejects withversion_conflictand nothing is changed. - New names, descriptions, announcements and avatars go through content moderation and reject with
content_rejectedif they fail. - If the group is disabled, the call rejects with
group_disabled.
Set a group avatar
Upload the group avatar first with purpose group_avatar, then write the returned public URL to the group profile (you can upload before the group exists):
Future<void> changeGroupAvatar(String groupId, String path) async {
final info = await im.files.upload(DRFile(path), purpose: UploadPurpose.groupAvatar).result;
await im.groups.update(groupId, avatarUrl: info.url);
}Member list
im.groups.members(groupId) returns a live list of group members (LiveList<GroupMember>): owner first, then admins, then regular members, each role ordered by join time. Only members can view it.
final members = im.groups.members(groupId);
final sub = members.stream.listen((s) {
if (s.error != null) {
showToast('成员列表加载失败:${s.error!.code}');
} else if (!s.loading) {
print(s.items.map((m) => m.groupNickname.isNotEmpty ? m.groupNickname : (m.nickname.isNotEmpty ? m.nickname : m.username)));
}
});
// 只看管理员、只看被禁言的成员
final admins = im.groups.members(groupId, role: GroupRole.admin);
final muted = im.groups.members(groupId, muted: true);
// 页面关闭时
await sub.cancel();
members.dispose();
admins.dispose();
muted.dispose();- The first time you open a group's member list, the SDK fetches all members and stores them locally. Later it only checks for changes, including after the app restarts. Filtering happens locally.
- Members joining or leaving, and changes to roles, group nicknames and mutes, show up in the list in real time, and
GroupMembersChangedis emitted. - Call
dispose()when you no longer show the list. In widgets, show it withLiveListBuilder, written the same way as My groups.
To show the group nicknames of just a few people (such as message senders), you don't need the whole list. Use lookupMembers (up to 100; non-members are left out):
final found = await im.groups.lookupMembers(groupId, ['lisi', 'wangwu']);To display names, use displayName(im, username, groupId: groupId) from package:deeprespond_im_flutter/render.dart, which takes the first non-empty value of friend remark, group nickname, nickname and username; see Users, friends and presence.
The group's online count (requires presence to be on and presence_scope to be all; otherwise it rejects locally with permission_denied):
final count = await im.groups.onlineCount(groupId);
print('${count.onlineCount} 人在线');Repeated queries for the same group within 30 seconds return the previous result.
Invite members
You can invite up to 100 people at once, with an optional message. Results come back one per person in request order, and one failure doesn't affect the others:
final results = await im.groups.invite(groupId, ['sunqi', 'zhouba'], message: '欢迎加入');
for (final r in results) {
if (!r.ok) showToast('${r.username}:${r.code}');
}Successful status values:
status | Description |
|---|---|
joined | Joined the group |
invited | The person must confirm; an invitation was sent. See Whether invitations need confirmation |
pending_review | The user is a regular member and the group requires approval for invitations; waiting for the owner or an admin |
already_member | Already a member |
Failed items have ok: false and carry a code. Common ones:
code | Cause |
|---|---|
permission_denied | The person is on the group blocklist (details['reason'] is group_blacklisted), or your server rejected it through the before-join-group callback |
limit_exceeded | The group is full (group_member_limit), or the person has joined the maximum number of groups (user_group_limit) |
user_blocked | The person has blocked the user |
not_friend | The app only allows inviting friends |
rate_limited | declined_cooldown: the invitation cooldown is on and the person declined this group's invitation or left this group within the last 24 hours. request_rate, group_join_rate: too many invitations or joins |
not_found | The user doesn't exist |
The invitation cooldown (group_invite_cooldown_enabled) is set in the console's group runtime policies:
- Off by default: after someone leaves or declines, you can invite them again right away.
- When on: for 24 hours after someone leaves or declines, this group can't invite them again.
- The cooldown only limits invitations. Being added by the server, applying and join links aren't affected; requirements like the person's confirmation and admin approval still apply.
- Separately, after a join application is declined, the applicant can't apply again for 24 hours. This always applies.
The call not throwing doesn't mean everyone joined. Check each item in results and tell the user who joined, who must confirm, who awaits admin approval, and why others failed.
The whole request fails when a regular member invites in a group whose member_invite is disabled (permission_denied), or when the group is disabled (group_disabled).
Remove members
The owner and admins can remove members (admins only regular members), up to 100 at once. In the results, removed means removed and not_member means they weren't a member. You can't remove yourself this way; use leave instead.
await im.groups.removeMembers(groupId, ['zhouba']);Set admins
The owner can make members admins or revoke admin. A group can have up to 20 admins (beyond that it rejects with limit_exceeded, details['reason'] admin_limit).
await im.groups.setRole(groupId, 'lisi', GroupRole.admin);
await im.groups.setRole(groupId, 'lisi', GroupRole.member); // 取消管理员Group nicknames and member attributes
Every member can change their own group nickname. The owner and admins can also change the group nickname and member attributes of members below them. Member attributes are merged key by key, keys set to null are deleted, and clearAttributes: true clears them all.
final me = im.auth.currentUser!.username;
await im.groups.updateMember(groupId, me, groupNickname: '李四-研发');
await im.groups.updateMember(groupId, 'wangwu', attributes: {'title': '后端'});Group nicknames are up to 64 characters; an empty string clears it. They go through content moderation.
Mute
Mute a member
The owner and admins can mute members (admins only regular members). Omitting the duration mutes permanently. A muted member sending a group message gets group_muted (details['reason'] is member).
await im.groups.mute(groupId, 'zhouba', durationSeconds: 3600); // 禁言 1 小时
await im.groups.unmute(groupId, 'zhouba');Durations range from 1 to 315360000 seconds (10 years). Whether the user is muted is in muted and mutedUntil of group.self; use them to disable the input box.
Mute everyone
When on, only the owner and admins can speak. Regular members sending a group message get group_muted (details['reason'] is all). An admin who is individually muted still can't speak.
await im.groups.setMuteAll(groupId, true);
await im.groups.setMuteAll(groupId, false);Group blocklist
A user added to the group blocklist is removed from the group (if in it) and can no longer apply, be invited or join through a link. The owner and admins manage the group blocklist; admins can only remove entries added by admins. Each group's blocklist holds up to 1000 people.
await im.groups.blacklist.add(groupId, 'spammer', reason: '发广告');
final page = await im.groups.blacklist.list(groupId, limit: 50);
for (final e in page.items) {
print('${e.username}:${e.reason ?? ''}');
}
await im.groups.blacklist.remove(groupId, 'spammer');Each item in the list is a GroupBlacklistEntry. The group blocklist only affects this group and is unrelated to the user-to-user blocklist.
Public groups and join requests
Search public groups
im.groups.publicGroups() lists public groups, newest first, optionally searching by name prefix. Each item has only public fields (groupId, type, name, avatarUrl, description, memberCount, joinMode, status). self is the user's information in the group, or null if not a member.
final page = await im.groups.publicGroups(namePrefix: '摄影', limit: 20);
for (final g in page.items) {
print('${g.name}(${g.memberCount} 人)${g.self != null ? ' 已加入' : ''}');
}
// 下一页:im.groups.publicGroups(namePrefix: '摄影', cursor: page.nextCursor)Apply to join
final status = await im.groups.applications.apply(groupId, message: '我也喜欢摄影');
if (status == 'joined') {
showToast('已加入');
} else if (status == 'pending') {
showToast('已提交申请,等待群主或管理员审批');
}If the group's application mode is free, the user joins immediately. If it's approval, a pending application is created, and the user joins once any owner or admin approves. The message is up to 256 characters. After an application is declined, the user can't apply to that group again for 24 hours (rate_limited, details['reason'] declined_cooldown), and a user can apply to the same group at most 5 times a day (pair_daily_limit). Private groups can't be applied to and return not_found; users on the group blocklist get permission_denied (group_blacklisted).
To withdraw a pending application and view the user's own applications:
await im.groups.applications.withdraw(groupId);
final mine = await im.groups.applications.mine();If the application has already been handled, withdraw rejects with version_conflict and details['status'] is its current status.
Approve applications and members' invitations
The owner and admins view and handle the group's requests with im.groups.requests. A request is identified by group + kind + username; there's no separate ID. Request kinds (GroupRequestKind):
GroupRequestKind | Server value | Description | Handled by |
|---|---|---|---|
application | application | A user applies to join a public group | Owner or admin |
inviteReview | invite_review | An invitation sent by a regular member in a group that requires approval | Owner or admin |
invitation | invitation | An invitation waiting for the invitee to confirm | The invitee; see the next section |
final page = await im.groups.requests.list(groupId, kind: GroupRequestKind.application, status: GroupRequestStatus.pending);
for (final r in page.items) {
print('${r.nickname.isNotEmpty ? r.nickname : r.username} 申请加入:${r.message ?? ''}');
}
final result = await im.groups.requests.approve(groupId, GroupRequestKind.application, 'sunqi');
await im.groups.requests.decline(groupId, GroupRequestKind.application, 'zhouba', reason: '仅限本校学生');approvereturnsjoined(the person joined),invited(a member's invitation was approved and the invitee must confirm, so an invitation was sent to them) orcanceled(the invitee can no longer receive this group's invitation and the request lapsed).- Join requirements are checked again when handling (group active, not on the group blocklist, member and group limits not reached). If they aren't met, the call rejects with the matching error and the request stays pending, so you can handle it again later.
- If someone else already handled the request, the call rejects with
version_conflictanddetails['status']is its current status. Expired or nonexistent requests reject withnot_found. - Decline reasons are up to 256 characters and shown to the applicant or inviter. After an application is declined, the applicant can't apply again for 24 hours.
- Regular members calling
requests.list()see only the invitations they sent. The inviter, owner and admins can withdraw a pending invitation withrequests.withdrawInvite(groupId, kind, username)(kindisinviteRevieworinvitation).
When a new request arrives or a request is handled, GroupRequestsChanged is emitted (list is pending, groupId is the group). Use it to refresh an approval page or show a red dot:
im.on<GroupRequestsChanged>().listen((e) {
if (e.list == 'pending') print('群 ${e.groupId} 有新的待审批请求');
});Pending requests expire 7 days after they were last made. For the full rules, see Join requests and invitations in the server docs.
Whether invitations need confirmation
Each user can choose how invitations to groups are handled: InviteMode.allowAny (allow_any) joins directly, and InviteMode.needConfirm (need_confirm) creates an invitation that the user must accept first. If the user hasn't set it, the default from the app's runtime policies applies (allow_any by default).
final settings = await im.groups.settings.get();
print(settings.inviteMode == null ? '跟随应用默认(${settings.effectiveInviteMode?.wire})' : settings.inviteMode!.wire);
await im.groups.settings.set(InviteMode.needConfirm);
await im.groups.settings.set(null); // 清除本人的设置,改用应用的默认值Handle received invitations
final page = await im.groups.invitations.received(status: GroupRequestStatus.pending);
for (final inv in page.items) {
final inviter = inv.inviter?.nickname ?? '有人';
print('$inviter 邀请你加入 ${inv.group?.name ?? '群聊'}');
}
final group = await im.groups.invitations.accept('1840012345678901');
await im.groups.invitations.decline('1840099999999999', reason: '暂时不参加');- Without
cursorandstatus,invitations.received()andapplications.mine()return the first page held in SDK memory without sending a request. The SDK refreshes that page on sign-in, on reconnect and when related notifications arrive, then emitsGroupRequestsChanged(listisinvitationsorapplications). - Accepting joins the group and returns the group information; the group appears in "My groups" right away. If the user already joined another way, the group information is returned too.
- After declining, the user can by default be invited again right away. Only when the app turns on the invitation cooldown can this group not invite them for 24 hours.
Join links
The owner and admins can create join links, including for private groups. Users who open the link join after confirming, with no approval needed. A group has only one valid link at a time; creating a new one or revoking it invalidates the old one immediately. A link also becomes invalid when its creator is no longer the owner or an admin. Links can be shown as QR codes.
final created = await im.groups.joinLink.create(groupId, expiresInSeconds: 86400);
print(created.link);
final current = await im.groups.joinLink.get(groupId); // 没有有效链接时三项都为 null
await im.groups.joinLink.revoke(groupId);Validity ranges from 86400 to 604800 seconds (1 to 7 days), 7 days by default.
The person opening the link (for example after scanning a QR code) previews it first, then confirms:
final preview = await im.groups.joinLink.preview(link);
if (preview.isMember) {
showToast('你已在群中');
} else {
final group = await im.groups.joinLink.join(link);
print(group.name);
}If the link is invalid, expired or revoked, or the group is disabled or dissolved, both preview and join reject with not_found. If the user is already a member, join returns the group information. Preview and join share a limit of 20 calls per user per minute.
Leave and dissolve
await im.groups.leave(groupId); // 退出群
await im.groups.dismiss(groupId); // 解散群,只有群主可以- Leave. The group is removed from "My groups" and its local member list is deleted. The owner leaving rejects with
owner_transfer_required; transfer ownership first, or dissolve the group if the owner is the only member. After leaving, the user can by default be invited again right away; only when the app turns on the invitation cooldown can this group not invite them for 24 hours. - Dissolve. This can't be undone. All members are notified and the group is removed from their "My groups"; all group files are deleted, and the local mute setting for this group is deleted too. Groups disabled by the platform can't be dissolved (
group_disabled).
When the user is removed or the group is dissolved, their group list also updates automatically. The reason can be seen from the group notice message in the conversation (see Messages).
Transfer ownership
The owner can transfer ownership to another member. The previous owner becomes a regular member, and join links they created become invalid.
final info = await im.groups.transferOwner(groupId, 'wangwu');
print(info.owner); // wangwuAPI reference
Every groupId parameter is a group ID string. Private groups are invisible to non-members, so any operation on them rejects with not_found. When the user isn't a member of a public group, member-only operations reject with not_group_member. Insufficient roles reject with permission_denied, operations blocked while a group is disabled reject with group_disabled, and calls while signed out reject with not_signed_in. These errors aren't repeated for each method below.
im.groups.list()
Returns a live list of the groups the user has joined (LiveList<GroupView>), sorted by group name. The snapshot's hasMore is always false. Call dispose() when you're done with it.
im.groups.get()
Reads one group.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
refresh | bool | No | Named. When true, skips local storage and queries the server. Default false |
Returns: Future<GroupView?>, null for groups the user can't see.
im.groups.lookup()
Queries group information in a batch.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupIds | List<String> | Yes | 1 to 100 group IDs |
Returns: Future<List<GroupInfo>>. Groups the user can't see are left out; public groups the user isn't in have only public fields.
Errors: local_validation (empty or more than 100).
im.groups.publicGroups()
Lists public groups, newest first, excluding disabled and dissolved groups. All parameters are named.
| Parameter | Type | Required | Description |
|---|---|---|---|
namePrefix | String? | No | Search by group name prefix |
cursor | String? | No | Cursor for the next page |
limit | int? | No | Items per page; default 20, max 100 |
Returns: Future<DRPage<GroupInfo>>.
im.groups.create()
Creates a group with the user as owner. Never retried automatically; see Create a group. All parameters are named.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Group name, up to 64 characters |
type | GroupType? | No | Group type; default private |
avatarUrl | String? | No | Group avatar URL |
description | String? | No | Description, up to 512 characters |
announcement | String? | No | Announcement, up to 2048 characters |
attributes | Map<String, String?>? | No | Custom attributes, visible to all members; up to 32 keys and 4 KB in total |
memberInvite | MemberInvite? | No | Whether regular members can invite others; default free |
joinMode | JoinMode? | No | Application mode for public groups; default approval. Can't be set for private groups |
members | List<String>? | No | Usernames of initial members, up to 100 |
message | String? | No | Invitation message for initial members |
Returns: Future<CreateGroupResult>.
Errors: invalid_argument, permission_denied (the app turned off client group creation), limit_exceeded (user_group_limit, app_group_limit), content_rejected, rate_limited, app_unavailable (app is read-only), timeout.
im.groups.update()
Edits the group profile and settings (owner and admins; only the owner can change the type), sending only the arguments passed. All parameters except groupId are named.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
type, name, avatarUrl, description, announcement, memberInvite, joinMode | Same as create | No | Fields to change, at least one |
attributes | Map<String, String?>? | No | Custom attributes, merged key by key; keys set to null are deleted |
clearAttributes | bool | No | When true, clears all custom attributes. Default false |
version | int? | No | The infoVersion you read. Nothing is changed if it doesn't match |
Returns: Future<GroupInfo>, the updated group.
Errors: invalid_argument (for example setting joinMode on a private group), content_rejected, version_conflict.
im.groups.transferOwner()
Transfers ownership (owner).
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
username | String | Yes | The new owner; must be a member |
Returns: Future<GroupInfo>.
Errors: not_group_member (the person isn't a member).
im.groups.setMuteAll()
Turns mute-everyone on or off (owner and admins).
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
on | bool | Yes | true to turn on, false to turn off |
Returns: Future<GroupInfo>.
im.groups.leave()
Leaves a group.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
Returns: Future<void>.
Errors: owner_transfer_required (the user is the owner).
im.groups.dismiss()
Dissolves a group (owner). Can't be undone.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
Returns: Future<void>.
im.groups.onlineCount()
Queries the group's online count. Repeated calls for the same group within 30 seconds return the previous result.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
Returns: Future<GroupOnlineCount>.
Errors: permission_denied (details['reason'] is presence_disabled: presence isn't on for the app; or presence_scope: presence visibility isn't all. Both are checked locally), rate_limited.
im.groups.members()
Returns a live list of group members (LiveList<GroupMember>). Only members can view it. Call dispose() when you're done with it.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
role | GroupRole? | No | Named. List only members with this role |
muted | bool? | No | Named. true lists only muted members, false only unmuted ones |
If fetching fails, the snapshot's error is the matching DRException. Throws not_signed_in when signed out.
im.groups.lookupMembers()
Queries members by username (members only).
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
usernames | List<String> | Yes | 1 to 100 usernames |
Returns: Future<List<GroupMember>>. Non-members are left out.
Errors: local_validation (empty or more than 100).
im.groups.invite()
Invites people to the group.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
usernames | List<String> | Yes | 1 to 100 usernames |
message | String? | No | Named. Invitation message |
Returns: Future<List<GroupMemberResult>>; see Invite members for each person's result.
Errors: local_validation, permission_denied (a regular member inviting in a group that doesn't allow it), content_rejected (the message).
im.groups.removeMembers()
Removes members (owner and admins).
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
usernames | List<String> | Yes | 1 to 100 usernames |
Returns: Future<List<GroupMemberResult>>; successful status is removed or not_member.
Errors: local_validation, invalid_argument (removing yourself).
im.groups.updateMember()
Changes a member's group nickname and member attributes: the member themself, or an owner or admin ranked above them.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
username | String | Yes | The member's username |
groupNickname | String? | No | Named. Group nickname, up to 64 characters. An empty string clears it |
attributes | Map<String, String?>? | No | Named. Member attributes, merged key by key; keys set to null are deleted. Up to 16 keys and 1 KB in total |
clearAttributes | bool | No | Named. When true, clears all member attributes. Default false |
Returns: Future<GroupMember>.
Errors: content_rejected (group nickname), not_group_member (the person isn't a member).
im.groups.setRole()
Makes a member an admin or revokes admin (owner).
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
username | String | Yes | The member's username |
role | GroupRole | Yes | The new role: admin or member |
Returns: Future<GroupMember>.
Errors: limit_exceeded (admin_limit), invalid_argument (applied to the owner).
im.groups.mute()
Mutes a member (owner and admins).
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
username | String | Yes | The member's username |
durationSeconds | int? | No | Named. Duration, 1 to 315360000 seconds. Permanent if omitted |
Returns: Future<GroupMember>.
im.groups.unmute()
Unmutes a member (owner and admins). You can't unmute yourself.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
username | String | Yes | The member's username |
Returns: Future<GroupMember>.
im.groups.blacklist.list()
Queries the group blocklist (owner and admins).
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
cursor | String? | No | Named. Cursor for the next page |
limit | int? | No | Named. Items per page; default 20, max 100 |
Returns: Future<DRPage<GroupBlacklistEntry>>.
im.groups.blacklist.add()
Adds a user to the group blocklist (owner and admins) and removes them from the group if they're in it. Succeeds if already on the list.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
username | String | Yes | Username |
reason | String? | No | Named. Reason |
Returns: Future<void>.
Errors: limit_exceeded (group_blacklist_limit), invalid_argument (blocking yourself).
im.groups.blacklist.remove()
Removes a user from the group blocklist. Admins can only remove entries added by admins.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
username | String | Yes | Username |
Returns: Future<void>.
im.groups.joinLink.get()
Gets the current valid join link (owner and admins).
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
Returns: Future<JoinLink>; all three fields are null when there's no valid link.
im.groups.joinLink.create()
Creates or recreates the join link (owner and admins); the old link stops working immediately.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
expiresInSeconds | int? | No | Named. Validity, 86400 to 604800 seconds; default 604800 |
Returns: Future<JoinLink>.
im.groups.joinLink.revoke()
Revokes the join link (owner and admins).
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
Returns: Future<void>.
im.groups.joinLink.preview()
Previews a join link.
| Parameter | Type | Required | Description |
|---|---|---|---|
link | String | Yes | The join link |
Returns: Future<JoinLinkPreview>.
Errors: not_found (the link is invalid, expired or revoked, or the group is disabled or dissolved), rate_limited.
im.groups.joinLink.join()
Joins through a join link. Returns the group information if already a member.
| Parameter | Type | Required | Description |
|---|---|---|---|
link | String | Yes | The join link |
Returns: Future<GroupInfo>.
Errors: not_found, permission_denied (on the group blocklist, or rejected by the before-join-group callback), limit_exceeded (the group is full or the user has joined the maximum number of groups), rate_limited.
im.groups.applications.apply()
Applies to join a public group.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
message | String? | No | Named. Message, up to 256 characters |
Returns: Future<String>: joined, pending or already_member.
Errors: not_found (private group), permission_denied (group_blacklisted, or rejected by the before-join-group callback), limit_exceeded (user_group_limit, group_member_limit), rate_limited (declined_cooldown, pair_daily_limit, request_rate, group_join_rate), content_rejected.
im.groups.applications.withdraw()
Withdraws the user's pending application. Succeeds if already withdrawn.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
Returns: Future<void>.
Errors: version_conflict (the application was already handled; details['status'] is its current status).
im.groups.applications.mine()
Queries the user's join applications, newest first. All parameters are named.
| Parameter | Type | Required | Description |
|---|---|---|---|
status | GroupRequestStatus? | No | List only applications with this status |
cursor | String? | No | Cursor for the next page |
limit | int? | No | Items per page |
Returns: Future<DRPage<GroupRequest>>. Without cursor and status, returns the first page held in SDK memory.
im.groups.invitations.received()
Queries group invitations the user received, newest first. Parameters are the same as applications.mine().
Returns: Future<DRPage<GroupRequest>>. Without cursor and status, returns the first page held in SDK memory.
im.groups.invitations.accept()
Accepts a group invitation.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
Returns: Future<GroupInfo>.
Errors: not_found (the invitation doesn't exist or has expired), permission_denied (on the group blocklist), limit_exceeded, rate_limited (group_join_rate), version_conflict (for example the invitation was withdrawn).
im.groups.invitations.decline()
Declines a group invitation. By default the user can be invited again right away; only when the app turns on group_invite_cooldown_enabled can this group not invite them for 24 hours.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
reason | String? | No | Named. Reason, up to 256 characters |
Returns: Future<void>.
im.groups.requests.list()
Queries the group's requests, newest first. The owner and admins see all kinds; regular members see only invitations they sent. Always queries the server.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
kind | GroupRequestKind? | No | Named. application, inviteReview or invitation |
status | GroupRequestStatus? | No | Named. pending, accepted, declined, canceled or expired |
cursor | String? | No | Named. Cursor for the next page |
limit | int? | No | Named. Items per page |
Returns: Future<DRPage<GroupRequest>>.
im.groups.requests.approve()
Approves an application or a member's invitation (owner and admins).
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
kind | GroupRequestKind | Yes | application or inviteReview |
username | String | Yes | The applicant or invitee |
Returns: Future<String>: joined, invited or canceled.
Errors: not_found (the request doesn't exist or has expired), version_conflict (someone else handled it), permission_denied (group_blacklisted), limit_exceeded, rate_limited (group_join_rate).
im.groups.requests.decline()
Declines an application or a member's invitation (owner and admins).
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
kind | GroupRequestKind | Yes | application or inviteReview |
username | String | Yes | The applicant or invitee |
reason | String? | No | Named. Reason, up to 256 characters |
Returns: Future<void>.
Errors: not_found, version_conflict, content_rejected (the reason).
im.groups.requests.withdrawInvite()
Withdraws a pending invitation: the inviter, the owner or an admin.
| Parameter | Type | Required | Description |
|---|---|---|---|
groupId | String | Yes | Group ID |
kind | GroupRequestKind | Yes | inviteReview or invitation |
username | String | Yes | The invitee |
Returns: Future<void>.
Errors: not_found, version_conflict.
im.groups.settings.get()
Gets how invitations to the user are handled.
Returns: Future<GroupInviteSettings>. inviteMode is the user's own setting, null if never set; effectiveInviteMode is the mode actually in effect.
im.groups.settings.set()
Sets how invitations to the user are handled.
| Parameter | Type | Required | Description |
|---|---|---|---|
mode | InviteMode? | Yes | allowAny or needConfirm. null clears the setting and uses the app default |
Returns: Future<void>.
Related events
Subscribe by type with im.on<T>(); see Events and errors.
| Event | Fields | Description |
|---|---|---|
GroupsChanged | upserted, removed (group IDs) | Group information changed (upserted), or groups left "My groups" (removed) |
GroupMembersChanged | groupId, usernames (List<String>?) | A locally stored member list changed; usernames: null means the whole list changed |
GroupRequestsChanged | list, groupId (String?) | list pending: this group's pending requests changed. invitations, applications: the first page of the user's received invitations or sent applications was refreshed |
Types
Common fields of server objects (which extend DRObject) are provided as getters, with times as UTC DateTimes. Every field the server returned is in raw (Map<String, Object?>), so you can read fields that have no getter.
GroupView
| Field | Type | Description |
|---|---|---|
groupId | String | Group ID |
group | GroupInfo | Group information; for groups the user has left, the last known information from before they left |
inMyGroups | bool | Whether it's in "My groups" |
GroupInfo
Members see every field. For public groups the user isn't in, only groupId, type, name, avatarUrl, description, memberCount, joinMode, status and self are present; the rest are null (or empty).
| Field | Type | Description |
|---|---|---|
groupId | String | Group ID |
type | String | Group type: private or public |
name | String | Group name; empty string if none |
avatarUrl | String | Group avatar URL; empty string if none |
description | String? | Description |
announcement | String? | Announcement |
announcementUpdatedAt | DateTime? | When the announcement was last changed |
announcementUpdatedBy | String? | Username of whoever last changed the announcement from a client |
attributes | Map<String, String>? | Custom attributes |
owner | String? | The owner's username |
memberInvite | String? | Whether regular members can invite others: free, approval, disabled |
joinMode | String? | Application mode for public groups: approval, free; null for private groups |
muteAll | bool | Whether mute-everyone is on |
maxMembers | int? | Member limit |
memberCount | int | Current member count, including the owner |
status | String | active, disabled or dismissed (dissolved) |
infoVersion | int? | Version of the group profile and settings; can be passed as version to update |
memberVersion | int? | Version of the member list |
createdAt | DateTime? | Creation time |
self | GroupSelf? | The user's information in the group; null if not a member |
raw['disabled_by'] is who disabled the group: tenant (the app) or platform; null when not disabled.
GroupSelf
| Field | Type | Description |
|---|---|---|
role | GroupRole? | The user's role |
groupNickname | String | The user's group nickname; empty string if none |
muted | bool | Whether the user is muted |
mutedUntil | DateTime? | When the mute ends; null for a permanent mute |
joinedAt | DateTime? | When the user joined |
CreateGroupResult
Extends GroupInfo, adding:
| Field | Type | Description |
|---|---|---|
results | List<GroupMemberResult> | Each initial member's result, in request order; an empty list when the group was confirmed created by a check after a timeout |
GroupMember
| Field | Type | Description |
|---|---|---|
username | String | Username |
nickname | String | Nickname from the user's profile |
avatarUrl | String | Avatar from the user's profile |
role | GroupRole? | owner, admin or member |
groupNickname | String | Group nickname in this group; empty string if none |
attributes | Map<String, String>? | Member attributes |
muted | bool | Whether muted |
mutedUntil | DateTime? | When the mute ends; null for a permanent mute |
joinedVia | String | How they joined: create (at creation), server (added by the server), invite, apply or link |
inviter | String? | The inviter's username |
joinedAt | DateTime? | When they joined |
GroupMemberResult
The result for one user in a batch operation: successes have status, failures have code.
| Field | Type | Description |
|---|---|---|
username | String | Username |
ok | bool | Whether it succeeded |
status | String? | On success: joined, invited, pending_review or already_member (invite, create), or removed or not_member (remove); null on failure |
code | String? | On failure: the error code |
message | String? | On failure: a description |
details | Map<String, Object?>? | On failure: details, with details['reason'] explaining why |
GroupOnlineCount
| Field | Type | Description |
|---|---|---|
onlineCount | int | Online count |
countedAt | DateTime? | When it was counted |
GroupRequest
A join application or invitation; each item from requests.list(), applications.mine() and invitations.received().
| Field | Type | Description |
|---|---|---|
groupId | String | Group ID |
kind | String | application, invite_review or invitation |
username | String | The request's target user: the applicant or invitee |
nickname | String | The target user's nickname |
avatarUrl | String | The target user's avatar |
inviter | GroupRequestUser? | The inviter's username, nickname and avatarUrl; null for applications |
message | String? | Application or invitation message |
reason | String? | Decline reason |
status | String | pending, accepted, declined, canceled or expired |
cancelCause | String? | Why it lapsed: withdrawn, joined, blacklisted, group_private, not_eligible, dismissed |
requestedAt | DateTime? | When it was last made |
expiresAt | DateTime? | When it expires; only set while pending |
handledAt | DateTime? | When it was handled |
handledBy | String? | Username of whoever handled it |
group | GroupBrief? | Only in the user's received invitations and sent applications: the group's groupId, name, avatarUrl and memberCount. null for private groups once the request is handled |
While a group is disabled, its pending requests show as canceled with cancelCause null, and are restored when the group is re-enabled.
GroupBlacklistEntry
| Field | Type | Description |
|---|---|---|
username | String | Username |
nickname | String | Nickname |
avatarUrl | String | Avatar |
reason | String? | Reason |
createdBy | String? | The owner or admin who added it; null when added by your server or the console |
createdByRole | String? | Role of whoever added it: owner, admin or server |
createdAt | DateTime? | When it was added |
JoinLink
| Field | Type | Description |
|---|---|---|
link | String? | The join link |
expiresAt | DateTime? | When it expires |
createdBy | String? | Who created it |
JoinLinkPreview
| Field | Type | Description |
|---|---|---|
groupId | String | Group ID |
name | String | Group name |
avatarUrl | String | Group avatar |
memberCount | int | Member count |
isMember | bool | Whether the user is already a member |
GroupInviteSettings
| Field | Type | Description |
|---|---|---|
inviteMode | InviteMode? | The user's own setting; null if never set |
effectiveInviteMode | InviteMode? | The mode in effect |
Enums
| Enum | Values |
|---|---|
GroupRole | owner, admin, member |
GroupType | private, public |
MemberInvite | free, approval, disabled |
JoinMode | approval, free |
GroupRequestKind | application, inviteReview (invite_review), invitation |
GroupRequestStatus | pending, accepted, declined, canceled, expired |
InviteMode | allowAny (allow_any), needConfirm (need_confirm) |
Each enum's wire is the server value.
