// members can be added by passing an array of user IDs const channel = client.channel("messaging", randomID, { members: ["userid1", "userid2", "userid3"], }); // or by passing objects const channel = client.channel("messaging", randomID, { members: [ { user_id: "userid1" }, { user_id: "userid2" }, { user_id: "userid3" }, ], });Channel members
Add channel members
Members can be added to a channel either when creating it or by using the addMembers method.
When creating a channel
let channelController = try ChatClient.shared.channelController( createChannelWithId: randomId, members: [ "userid1", "userid2", "userid3" ] ) // Calling synchronize() is required to persist the channel to the server. channelController.synchronize()ChannelGetResponse channel = Channel.getOrCreate("messaging", "channel-1") .data( ChannelRequestObject.builder() .createdBy(user1) .members( users.stream().map((x) -> ChannelMemberRequestObject.builder().user(x).build()) .collect(Collectors.toList())) .build()) .request();// members can be added by passing an array of user IDs var response = await _channelClient.GetOrCreateAsync("messaging", "my-channel-id", createdBy: "user-1", members: new[] { "user-2", "user-3" }); var channel = response.Channel; // or by passing objects var response2 = await _channelClient.GetOrCreateAsync("messaging", new ChannelGetRequest { Data = new ChannelRequest { CreatedBy = new UserRequest { Id = "user-1" }, Members = new List<ChannelMember> { new() { UserId = "user-2" }, new() { UserId = "user-3" }, } }, }); var channel2 = response2.Channel;// members can be added by passing a list of user IDs channelClient.create( memberIds = listOf("userid1", "userid2", "userid3"), extraData = emptyMap(), ).enqueue() // or by passing objects val params = CreateChannelParams( members = listOf( MemberData(userId = "userid1"), MemberData(userId = "userid2"), MemberData(userId = "userid3", extraData = mapOf("custom" to "value")), ), extraData = emptyMap(), ) channelClient.create(params).enqueue()Using addMembers method
await channel.addMembers(["userid1", "userid2"]); // you can set channel_role in the meantime await channel.addMembers([ { user_id: "userid1", channel_role: "channel_moderator" }, ]);// members can be added by passing an array of user IDs let channelController = ChatClient.shared.channelController(for: randomId) channelController.addMembers(userIds: ["userid1", "userid2", "userid3"]) // or by passing objects let channelController = ChatClient.shared.channelController(for: randomId) channelController.addMembers([ MemberInfo(userId: "userid1"), MemberInfo(userId: "userid2"), MemberInfo(userId: "userid3", extraData: ["custom": .string("value")]) ])Channel.update("messaging", "channel-1").addMember("user-3").request();await _channelClient.AddMembersAsync("messaging", "my-channel-id", new[] { "user-2", "user-3" });// members can be added by passing a list of user IDs channelClient.addMembers(listOf("userid1", "userid2", "userid3")).enqueue() // or by passing objects val params = AddMembersParams( members = listOf( MemberData(userId = "userid1"), MemberData(userId = "userid2"), MemberData(userId = "userid3", extraData = mapOf("custom" to "value")), ), ) channelClient.addMembers(params).enqueue()Note: You can only add up to 100 members at once.
Message parameter
You can optionally include a message object to allow client-side SDKs to generate a system message. This feature is available for both adding and removing members.
await channel.addMembers(["userid1"], { text: "Tommaso joined the channel." }); // using server-side client, you need to specify the sender user_id await channel.addMembers(["userid1"], { text: "Tommaso joined the channel.", user_id: "userid2", });channelController.addMembers( userIds: ['userid1'], message: "Tommaso joined the channel." ) { error in // handle error }Channel.update("messaging", "channel-1").addMember("user-3") .message(MessageRequestObject.builder().text("User 3 joined!").build()).request();await _channelClient.AddMembersAsync("messaging", "my-channel-id", new[] { "user-2" }, new MessageRequest { Text = "Tommaso joined the channel.", UserId = "user-1", // Message sender }, options: null);channelClient.addMembers( memberIds = listOf("userid1"), systemMessage = Message(text = "New user joined this channel."), ).enqueue()Hide history
When members join a channel, you can specify whether they have access to the channel’s history.
By default, new members can see the history. To hide it, set the hide_history parameter to true .
await channel.addMembers(["userid1"], undefined, { hide_history: true });channelController.addMembers(userIds: ['userid1'], hideHistory: true)Channel.update("messaging", "channel-1").addMember("user-3").hideHistory(true).request();await _channelClient.AddMembersAsync("messaging", "my-channel-id", new[] { "user-2" }, msg: null, new AddMemberOptions { HideHistory = true });channelClient.addMembers( memberIds = listOf("userid1"), hideHistory = true, ).enqueue()Channel member custom data
Custom data can be added at the channel member level. Ensure it does not exceed 5KB.
// add custom data while creating the channel const channel = client.channel("messaging", randomID, { members: [ { user_id: "userid1", key1: "value1" }, { user_id: "userid2", key1: "value1" }, { user_id: "userid3", key2: "value2" }, ], }); // add custom data with `addMembers` method await channel.addMembers([{ user_id: "userid1", key1: "value1" }]);channelController.addMembers([ MemberInfo(userId: "userid1", extraData: ["key1": .string("value1")]), MemberInfo(userId: "userid2", extraData: ["key2": .string("value2")]), MemberInfo(userId: "userid3", extraData: ["key3": .string("value3")]) ])await _channelClient.AddMembersAsync("messaging", "my-channel-id", new[] { "user-2" }); var partialRequest = new ChannelMemberPartialRequest { UserId = "user-2", Set = new Dictionary<string, object> { { "hat", "blue" }, // Channel member custom data is separate from user custom data }, }; await _channelClient.UpdateMemberPartialAsync("messaging", "my-channel-id", partialRequest);// add custom data while creating the channel val params = CreateChannelParams( members = listOf( MemberData(userId = "userid1", extraData = mapOf("key1" to "value1"), MemberData(userId = "userid2", extraData = mapOf("key2" to "value2"), MemberData(userId = "userid3", extraData = mapOf("key3" to "value3")), ), extraData = emptyMap(), ) channelClient.create(params).enqueue() // add custom data with `addMembers` method val params = AddMembersParams( members = listOf( MemberData(userId = "userid1", extraData = mapOf("key1" to "value1"), MemberData(userId = "userid2", extraData = mapOf("key2" to "value2"), MemberData(userId = "userid3", extraData = mapOf("key3" to "value3")), ), ) channelClient.addMembers(params).enqueue()Remove channel members / Leave a channel
Remove channel members
removeMembers method allows you to remove members from a channel.
await channel.removeMembers(["userid1", "userid2"]);channelController.removeMembers(userIds: ["userid1", "userid2"])await _channelClient.RemoveMembersAsync("messaging", "my-channel-id", new[] { "user-2", "user-3" });channelClient.removeMembers(listOf("userid1", "userid2")).enqueue()Note: You can only remove up to 100 members at once.
Leave a channel
Users can leave a channel without moderator-level permissions.
Ensure channel members have the Leave Own Channel permission enabled.
// remove own channel membership await channel.removeMembers(["myuserid"]);channelController.removeMembers(userIds: ["myuserid"])await _channelClient.RemoveMembersAsync("messaging", "my-channel-id", new[] { "user-2" });channelClient.removeMembers(listOf("myuserid")).enqueue()You can familiarize yourself with all permissions in Permissions section
Add / Remove moderators to a channel
Using the addModerators method adds the given users as moderators (or updates their role to moderator if already members), while demoteModerators removes the moderator status.
Add moderators
The addModerators method adds specified users as moderators to a channel. If the users are already members, their role is upgraded to moderator.
await channel.addModerators(["userid1", "userid2"]);await _channelClient.AddModeratorsAsync("messaging", "my-channel-id", new[] { "user-2", "user-3" });Remove moderators
The demoteModerators method removes the moderator role from specified users.
await channel.demoteModerators(["userid1"]);await _channelClient.DemoteModeratorsAsync("messaging", "my-channel-id", new[] { "user-2", "user-3" });These operations can only be performed server-side, and a maximum of 100 moderators can be added or removed at once.
Update channel members
Channel members can be partially updated. Only custom data and channel roles are eligible for modification.
You can set or unset fields, either separately or in the same call.
// set some fields await channel.updateMemberPartial( { set: { key1: "new value 1", key2: "new value 2", channel_role: "channel_moderator", }, }, { userId: "jane" }, ); // unset some fields await channel.updateMemberPartial( { unset: ["key1", "key2"], }, { userId: "jane" }, ); // set / unset in the same call await channel.updateMemberPartial( { set: { key1: "new value 1", key2: "new value 2", }, unset: ["key3"], }, { userId: "jane" }, );// set some fields let memberController = ChatClient.shared.memberController( userId: someUserId, in: someChannelId ) memberController.partialUpdate(extraData: [ "key1": .string("value1"), "key2": .string("value2") ]) // unset some fields memberController.partialUpdate(extraData: nil, unsetProperties: ["key1", "key2"]) // set / unset in the same call memberController.partialUpdate( extraData: [ "key1": .string("value1"), "key2": .string("value2"), ], unsetProperties: ["key3"] )// set some fields member, err := ch.PartialUpdateMember(ctx, members[0], PartialUpdate{ Set: map[string]interface{}{ "color": "red", }, }) // unset some fields member, err := ch.PartialUpdateMember(ctx, members[0], PartialUpdate{ Unset: []string{"age"}, }) // set / unset in the same call member, err := ch.PartialUpdateMember(ctx, members[0], PartialUpdate{ Set: map[string]interface{}{ "color": "red", }, Unset: []string{"age"}, })$userId = "amy"; // set some fields $response = $this->channel->updateMemberPartial($userId, ["hat" => "blue"]); // unset some fields $response = $this->channel->updateMemberPartial($userId, null, ["hat"]); // set / unset in the same call $response = $this->channel->updateMemberPartial($userId, ["hat" => "blue"], ["hat"]);user_id = "amy" // set some fields response = channel.update_member_partial(user_id, to_set={"hat": "blue"}) // unset some fields response = channel.update_member_partial(user_id, to_set=None, to_unset=["hat"]) // set / unset in the same call response = channel.update_member_partial(user_id, to_set={"color": "red"}, to_unset=["hat"])# require 'stream-chat' user_id = "amy" // set some fields response = channel.update_member_partial(user_id, set: { 'color' => 'red' }) // unset some fields response = channel.update_member_partial(user_id, set: nil, unset: ['hat']) // set / unset in the same call response = channel.update_member_partial(user_id, set: { 'color' => 'red' }, unset: ['hat'])// set some fields Channel.updateMemberPartial(channel.getType(), channel.getId(), "user-1") .setValue("custom_key", "custom_value") .setValue("channel_role", "channel_moderator") .request(); // unset some field Channel.updateMemberPartial(channel.getType(), channel.getId(), "user-1") .unsetValue("custom_key") .request(); // set / unset in the same call Channel.updateMemberPartial(channel.getType(), channel.getId(), "user-1") .setValue("color", "red") .unsetValue("age") .request();// Set some fields var memberResponse = await _channelClient.UpdateMemberPartialAsync("messaging", "my-channel-id", new ChannelMemberPartialRequest { UserId = "user-2", Set = new Dictionary<string, object> { { "hat", "blue" }, { "score", 1000 }, }, }); // Unset some fields var memberResponse2 = await _channelClient.UpdateMemberPartialAsync("messaging", "my-channel-id", new ChannelMemberPartialRequest { UserId = "user-2", Unset = new[] { "hat", "score" }, }); // Set / Unset in a single request var memberResponse3 = await _channelClient.UpdateMemberPartialAsync("messaging", "my-channel-id", new ChannelMemberPartialRequest { UserId = "user-2", Set = new Dictionary<string, object> { { "hat", "blue" }, }, Unset = new[] { "score" }, });// set some fields channelClient.partialUpdateMember( userId = "userid1", set = mapOf( "key1" to "new value 1", "key2" to "new value 2", ), ).enqueue() // unset some fields channelClient.partialUpdateMember( userId = "userid1", unset = listOf("key1", "key2"), ).enqueue() // set / unset in the same call channelClient.partialUpdateMember( userId = "userid1", set = mapOf( "key1" to "new value 1", "key2" to "new value 2", ), unset = listOf("key3"), ).enqueue()Query channel members
The queryMembers endpoint enables listing and paginating channel members. It offers filtering options to efficiently retrieve member information. This feature is particularly useful when you need to search through or display a comprehensive overview of channel membership.
Pagination and ordering
By default, members are ordered from oldest to newest and can be paginated using offset-based pagination or by the created_at or user_id fields.
While pagination by offset is the simplest to implement, it can lead to incorrect results if the list of members changes during pagination.
The recommended approach is to sort created_at or user_id for more reliable results.
await channel.queryMembers({}, sort, {}); // returns up to 100 members ordered by created_at descending let sort = { created_at: -1 }; await channel.queryMembers({}, sort, {}); // returns up to 100 members ordered by user_id descending sort = { user_id: -1 }; await channel.queryMembers({}, sort, {}); // paginate by user_id in descending order sort = { user_id: 1 }; let options = { user_id_lt: lastMember.user_id }; await channel.queryMembers({}, sort, options); // paginate by created at in ascending order sort = { created_at: -1 }; options = { created_at_before: lastMember.created_at }; await channel.queryMembers({}, sort, options); // paginate using offset options = { offset: 20 }; await channel.queryMembers({}, sort, {});// creates the controller and synchronizes the first batch of 25 members let query = ChannelMemberListQuery(cid: channelId) let memberListController = client.memberListController(query: query) memberListController.synchronize() // changing the default page size to 50 let query = ChannelMemberListQuery(cid: channelId, pageSize: 50) let memberListController = client.memberListController(query: query) memberListController.synchronize() // changing the sort order by user_id in descending order let query = ChannelMemberListQuery(cid: channelId, sort: [.init(key: .userId, isAscending: false)]) let memberListController = client.memberListController(query: query) memberListController.synchronize()// Simple members query var response = await _channelClient.QueryMembersAsync(new QueryMembersRequest { Type = "messaging", Id = "my-channel-id", FilterConditions = new Dictionary<string, object>(), }); // Query members with filter and sort var response2 = await _channelClient.QueryMembersAsync(new QueryMembersRequest { Type = "messaging", Id = "my-channel-id", FilterConditions = new Dictionary<string, object> { { "color", "blue" }, // Filters by member custom data (not user custom data) }, Sorts = new[] { new SortParameter { Field = "created_at", Direction = SortDirection.Descending, }, }, }); // Query members with limit and pagination var response3 = await _channelClient.QueryMembersAsync(new QueryMembersRequest { Type = "messaging", Id = "my-channel-id", FilterConditions = new Dictionary<string, object> { { "color", "blue" }, // Filters by member custom data (not user custom data) }, Limit = 50, // Take 50 records Offset = 50, // Skip the first 50 records. Use for pagination -> calc from limit & page Sorts = new[] { new SortParameter { Field = "created_at", Direction = SortDirection.Descending, }, }, });// Returns the first batch of 25 members channelClient.queryMembers( offset = 0, limit = 25, filter = Filters.neutral(), sort = QuerySortByField() ).enqueue() // Sort the members by user_id in descending order channelClient.queryMembers( offset = 0, limit = 3, filter = Filters.neutral(), sort = QuerySortByField.descByName("user_id") ).enqueue()Here’s some example of how you can query the list of members:
// query members by user.name channel.queryMembers({'name':'tommaso'}) // autocomplete members by user name channel.queryMembers({name:{"$autocomplete":'tomm'}}) // query member by id channel.queryMembers({user_id:'tommaso'}) // query multiple members by id channel.queryMembers({user_id:{'$in:'['tommaso','thierry']}}) // query channel moderators channel.queryMembers({is_moderator:true}) // query for banned members in channel channel.queryMembers({banned:true}) // query members with pending invites channel.queryMembers({invite:'pending'}) // query members who joined the channel directly or accepted an invite channel.queryMembers({joined: true}) // query members who have rejected invite or have pending invite channel.queryMembers({joined: false} // query all the members channel.queryMembers({}) // order results by member created at descending channel.queryMembers({}, {created_at:-1}) // query by user.email client.queryMembers({ 'user.email':'awesome@getstream.io' }) // you can also query members by custom data // subscription is a custom field client.queryMembers({ 'subscription':'gold_plan' })// query members by user.name let query = ChannelMemberListQuery(cid: channelId, filter: .equal(.name, to: "tommaso")) let memberListController = client.memberListController(query: query) memberListController.synchronize() // autocomplete members by user name let query = ChannelMemberListQuery(cid: channelId, filter: .autocomplete(.name, text: "tomm")) let memberListController = client.memberListController(query: query) memberListController.synchronize() // query member by id let query = ChannelMemberListQuery(cid: channelId, filter: .equal(.id, to: "tommaso")) let memberListController = client.memberListController(query: query) memberListController.synchronize() // query multiple members by id let query = ChannelMemberListQuery(cid: channelId, filter: .in(.id, values: ["tommaso", "thierry"])) let memberListController = client.memberListController(query: query) memberListController.synchronize() // query channel moderators let query = ChannelMemberListQuery(cid: channelId, filter: .equal(.isModerator, to: true)) let memberListController = client.memberListController(query: query) memberListController.synchronize() // query for banned members in channel let query = ChannelMemberListQuery(cid: channelId, filter: .equal(.banned, to: true)) let memberListController = client.memberListController(query: query) memberListController.synchronize() // query members who joined the channel directly or accepted an invite let query = ChannelMemberListQuery(cid: channelId, filter: .equal(.joined, to: true)) let memberListController = client.memberListController(query: query) memberListController.synchronize() // query members who have rejected invite or have pending invite let query = ChannelMemberListQuery(cid: channelId, filter: .equal(.joined, to: false)) let memberListController = client.memberListController(query: query) memberListController.synchronize() // you can also query members by custom data by providing a string directly let query = ChannelMemberListQuery(cid: channelId, filter: .equal("subscription", to: "gold_plan")) let memberListController = client.memberListController(query: query) memberListController.synchronize()// Get members with pending invites var response = await _channelClient.QueryMembersAsync(new QueryMembersRequest { Type = "messaging", Id = "my-channel-id", FilterConditions = new Dictionary<string, object> { { "invite", "pending" }, }, }); // Search by name autocomplete var response2 = await _channelClient.QueryMembersAsync(new QueryMembersRequest { Type = "messaging", Id = "my-channel-id", FilterConditions = new Dictionary<string, object> { { "name", new Dictionary<string, string> { { "$autocomplete", "tomm" } } }, }, }); // Get moderators var response3 = await _channelClient.QueryMembersAsync(new QueryMembersRequest { Type = "messaging", Id = "my-channel-id", FilterConditions = new Dictionary<string, object> { { "channel_role", "channel_moderator" }, }, }); // Get members who have joined the channel var response4 = await _channelClient.QueryMembersAsync(new QueryMembersRequest { Type = "messaging", Id = "my-channel-id", FilterConditions = new Dictionary<string, object> { { "joined", true }, }, }); // Get banned members var response5 = await _channelClient.QueryMembersAsync(new QueryMembersRequest { Type = "messaging", Id = "my-channel-id", FilterConditions = new Dictionary<string, object> { { "banned", true }, }, }); // Get members by custom data var response6 = await _channelClient.QueryMembersAsync(new QueryMembersRequest { Type = "messaging", Id = "my-channel-id", FilterConditions = new Dictionary<string, object> { { "subscription", "gold_plan" }, }, }); // Get members by user email var response7 = await _channelClient.QueryMembersAsync(new QueryMembersRequest { Type = "messaging", Id = "my-channel-id", FilterConditions = new Dictionary<string, object> { { "user.email", "example@getstream.io" }, }, });// query members by user.name channelClient.queryMembers( offset = 0, limit = 10, filter = Filters.eq("name", "tommaso"), sort = QuerySortByField() ).enqueue() // autocomplete members by user name channelClient.queryMembers( offset = 0, limit = 10, filter = Filters.autocomplete("name", "tomm"), sort = QuerySortByField() ).enqueue() // query member by id channelClient.queryMembers( offset = 0, limit = 10, filter = Filters.eq("id", "tommaso"), sort = QuerySortByField() ).enqueue() // query multiple members by id channelClient.queryMembers( offset = 0, limit = 10, filter = Filters.`in`("id", listOf("tommaso", "thierry")), sort = QuerySortByField() ).enqueue() // query channel moderators channelClient.queryMembers( offset = 0, limit = 10, filter = Filters.eq("is_moderator", true), sort = QuerySortByField() ).enqueue() // query for banned members in channel channelClient.queryMembers( offset = 0, limit = 10, filter = Filters.eq("banned", true), sort = QuerySortByField() ).enqueue() // query members who joined the channel directly or accepted an invite channelClient.queryMembers( offset = 0, limit = 10, filter = Filters.eq("joined", true), sort = QuerySortByField() ).enqueue() // query members who have rejected invite or have pending invite channelClient.queryMembers( offset = 0, limit = 10, filter = Filters.eq("joined", false), sort = QuerySortByField() ).enqueue() // you can also query members by custom data by providing a string directly channelClient.queryMembers( offset = 0, limit = 10, filter = Filters.eq("subscription", "gold_plan"), sort = QuerySortByField() ).enqueue()Query Parameters
| name | type | description | default | optional |
|---|---|---|---|---|
| filters | object | Query filters to use. You can query on any of the custom fields defined above | {} | |
| sort | object | Sort parameters | { created_at:1 } | ✓ |
| options | object | Pagination options | { limit:100, offset:0 } | ✓ |
By default when query members does not have any filter and it will match all members on your channel.
Member Queryable Built-In Fields
The following fields can be used to filter channel members, along with any custom data associated with them:
| Name | Type | Description | Supported operators |
|---|---|---|---|
| id | string | User ID | $eq - $in |
| name | string | User name | $eq - $in - $autocomplete - $q |
| channel_role | string | Member role | $eq |
| banned | boolean | Ban status | $eq |
| invite | string accepted values: - pending - accepted - rejected | Invite status | $eq |
| joined | boolean | Whether user joined the channel or not | $eq |
| created_at | string (RFC3339) | Time when the member was created | $eq - $gt - $gte - $lt - $lte |
| updated_at | string (RFC3339) | Time when the member was updated | $eq - $gt - $gte - $lt - $lte |
| last_active | string (RFC3339) | Last time the member was active | $eq - $gt - $gte - $lt - $lte |
| cid | string | Channel CID | $eq |
| user.email | string | User’s email property | $eq - $in - $autocomplete |
Query options
| name | type | description | default | optional |
|---|---|---|---|---|
| limit | integer | Number of members to return | 100 | ✓ |
| offset | integer | Offset (max is 1000) | 0 | ✓ |
| user_id_lt | string | Pagination option: excludes members with ID greater or equal the value | ✓ | |
| user_id_lte | string | Pagination option: excludes members with ID greater than the value | ✓ | |
| user_id_gt | string | Pagination option: excludes members with ID less or equal the value | ✓ | |
| user_id_gte | string | Pagination option: excludes members with ID less than the value | ✓ | |
| created_at_after | string | Pagination option: select members created after the date (RFC399) | ✓ | |
| created_at_before | string | Pagination option: select members created before the date (RFC399) | ✓ | |
| created_at_before_or_equal | string | Pagination option: select members created before or equal the date (RFC399) | ✓ | |
| created_at_after_or_equal | string | Pagination option: select members created after or equal the date (RFC399) | ✓ |
Response
| Field name | Description |
|---|---|
| Members | The list of members matching the query |