Channel Members

Channel members are users who have been added to a channel and can participate in conversations. This page covers how to manage channel membership, including adding and removing members, controlling message history visibility, and managing member roles.

Adding and Removing Members

Adding Members

Using the addMembers() method adds the given users as members to a channel.

import StreamChat

let controller = chatClient.channelController(for: .init(type: .messaging, id: "general"))

// Controllers

controller.addMembers(userIds: ["thierry", "josh"])

// State layer (async-await)

try await chat.addMembers(["thierry", "josh"])
Info:

Note: You can only add/remove up to 100 members, and invite up to 100, at once.

Members can also be added when creating a channel:

let channelId = ChannelId(type: .messaging, id: "general")
let channelController = try chatClient.channelController(
  createChannelWithId: channelId,
  members: ["thierry", "tommaso"]
)
channelController.synchronize { error in
  if let error = error {
    print(error)
  }
}

Member Limits and Concurrent Adds

Stream does not limit how many members a channel can have. There is no maximum member setting on channel types or channels. The only limit is per request (see the note above).

If your application caps channel size, enforce the cap in your backend. Stream does not serialize member changes on a channel, so two requests that each add a member to a channel at your cap can both succeed. Checking the member count before adding does not prevent this, because both requests can read the count before either adds.

member_count counts every membership, including bots and members with a pending or rejected invite. It is updated asynchronously on larger channels, so it can lag behind concurrent changes. It is not suitable for enforcing a cap. To count only some members, such as people but not a bot, keep that count yourself.

To make a cap reliable:

  • Add members only from your backend, and serialize changes per channel, for example with a lock or queue keyed by the channel ID.
  • In the channel type's permissions, remove the grants that let users change membership from the client, for every role that holds them. That is every grant whose ID starts with create-channel, update-channel-members, add-own-channel-membership or remove-own-channel-membership, including the -owner and -any-team variants. The -owner variants matter because a channel's creator owns it, including when your backend creates the channel with that user as created_by_id. This routes every membership change, leaving included, through your backend. create-channel matters too, because creating a channel can add up to 100 members and 100 invites in the same call. See the permissions reference for which roles hold them by default.
  • Server-side requests are not subject to these permissions, so your backend can keep managing membership.

Removing Members

Using the removeMembers() method removes the given users from the channel.

// Controllers

controller.removeMembers(userIds: ["tommaso"])

// State layer (async-await)

try await chat.removeMembers(["tommaso"])

Removal and Channel Access

Removing a member ends their membership. It does not always end their access to the channel.

When you remove a member, within the same request:

  • Their membership ends, so they stop receiving notifications, push and unread counts for the channel.
  • Stream attempts to unsubscribe every device that user has connected, not only the active one.

The unsubscribe is best-effort. A successful response confirms the membership change. It does not guarantee that every device has already stopped receiving the channel, because a presence lookup or an unsubscribe can fail without failing the request.

Clients that lose read access are then disconnected from the channel, and may receive a channel.kicked event. That event is not guaranteed, since a client already unsubscribed by the step above is no longer enumerated. Use notification.removed_from_channel to handle membership removal on the client. Events already in flight may still arrive.

Warning:

Whether a removed member loses read access depends on your channel type's permissions, not on the removal. This matters in two common cases:

  • On channel types that grant read access to non-members, such as livestream, membership grants no extra read permission.
  • The user who created the channel can read it as its owner, on any channel type, independently of membership.

In both cases the removed user keeps receiving the channel and can start watching it again. To revoke read access, change the channel type's permissions so the resulting role cannot read the channel.

Banning is not a substitute. A channel ban stops the user sending messages, but it does not remove read access or unsubscribe watchers, so a banned user whose role still grants read can keep watching the channel.

Leaving a Channel

Users can leave a channel without moderator-level permissions. Ensure channel members have the Leave Own Channel permission enabled.

If you enforce a member cap from your backend, leaving goes through your backend too. See Member Limits and Concurrent Adds.

channelController.removeMembers(userIds: ["john"]) { error in
  // …
}
Info:

You can familiarize yourself with all permissions in the Permissions section.

Hide History

When members join a channel, you can specify whether they have access to the channel's message history. By default, new members can see the history. Set hide_history to true to hide it for new members.

// Controllers

channelController.addMembers(
  userIds: ["thierry"],
  hideHistory: true
) { error in
  // …
}

// State layer (async-await)

try await chat.addMembers(["thierry"], hideHistory: true)

Hide History Before a Specific Date

Alternatively, hide_history_before can be used to hide any history before a given timestamp while giving members access to later messages. The value must be a timestamp in the past in RFC 3339 format. If both parameters are defined, hide_history_before takes precedence over hide_history.

// Controllers

let cutoff = Calendar.current.date(byAdding: .day, value: -7, to: Date())
channelController.addMembers(
  userIds: ["thierry"],
  hideHistoryBefore: cutoff
) { error in
  // …
}

// State layer (async-await)

let cutoff = Calendar.current.date(byAdding: .day, value: -7, to: Date())
try await chat.addMembers(["thierry"], hideHistoryBefore: cutoff)

System Message Parameter

You can optionally include a message object when adding or removing members that client-side SDKs will use to display a system message. This works for both adding and removing members.

// Controllers

channelController.addMembers(
  userIds: ["tommaso"],
  message: "Tommaso joined the channel"
) { error in
  // …
}

// State layer (async-await)

try await chat.addMembers(["tommaso"], systemMessage: "Tommaso joined the channel")

Adding and Removing Moderators

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

JavaScript
await channel.addModerators(["thierry", "josh"]);

Remove Moderators

JavaScript
await channel.demoteModerators(["tommaso"]);
Info:

These operations can only be performed server-side, and a maximum of 100 moderators can be added or removed at once.

Member Custom Data

Custom data can be added at the channel member level. This is useful for storing member-specific information that is separate from user-level data. Ensure custom data does not exceed 5KB.

Adding Custom Data

channelController.addMembers([
	MemberInfo(userId: "userid1", extraData: ["key1": .string("value1")]),
	MemberInfo(userId: "userid2", extraData: ["key2": .string("value2")]),
	MemberInfo(userId: "userid3", extraData: ["key3": .string("value3")])
])

Updating Member Data

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
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 and unset in the same call
memberController.partialUpdate(
	extraData: [
		"key1": .string("value1"),
		"key2": .string("value2"),
	],
	unsetProperties: ["key3"]
)