Multi-Tenant & Teams

Many apps that add chat have customers of their own. If you’re building something like Slack, or a SaaS application like InVision you want to make sure that one customer can’t read the messages of another customer. Stream Chat can be configured in multi-tenant mode so that users are organized in separated teams that cannot interact with each other.

Teams

Stream Chat has the concept of teams for users and channels. The purpose of teams is to provide a simple way to separate different groups of users and channels within a single application.

If a user belongs to a team, the API will ensure that such user will only be able to connect to channels from the same team. Features such as user search are limited so that a user can only search for users from the same team by default.

In legacy permission system users can never access users nor channels from other teams. In Permissions V2 it is possible to alter this behavior using multi-tenant permissions.

When enabling multi-tenant mode all user requests will always ensure that the request applies to a team the user belongs to. For instance, if a user from team “blue” tries to delete a message that was created on a channel from team “red” the API will return an error. If user doesn’t have team set, it will only have access to users and channels that don’t have team.

Enable Teams for your application

In order to use Teams, your application must have multi-tenant mode enabled. You can ensure your app is in multi-tenant mode by calling the Application Settings endpoint.

const client = new StreamChat("{{ api_key }}", "{{ api_secret }}");

// Enable multi-tenant in app settings.
await client.updateAppSettings({
  multi_tenant_enabled: true,
});

You only need to activate multi-tenant once per application.

Make sure to activate multi-tenant before using teams.

Do not turn off multi-tenant on an application without very careful consideration as this will turn off teams checking which gives users the ability to access all channels and messages across all teams.

User teams

When using teams, users must be created from your back-end and specify which teams they are a member of.

// creates or updates a user from backend to be part of the "red" and "blue" teams
client.upsertUser({ id, teams: ["red", "blue"] });

A user can be a member of a maximum of 250 teams. Team name is limited to 100 bytes

User teams are included in all User object payloads. We recommend to have short team names to reduce response payload sizes

In Permissions v1, user teams can only be changed using server-side auth. This ensures users can’t change their own team membership. In Permissions v2 it is possible to update user teams from client-side if UpdateUserTeam action is granted to the user

Channel team

Channels can be associated with a team. Users can create channels client-side but if their user is part of a team, they will have to specify a team or the request will be rejected with an error.

// Creates the red-general channel for the red team
client.createChannel(
  channelType = "messaging",
  channelId = "red-general",
  extraData = mapOf("team" to "red")
).enqueue { result ->
  if (result.isSuccess) {
    val channel = result.data()
  } else {
    // Handle result.error()
  }
}

Channel teams allows you to ensure proper permission checking for a multi tenant application. Keep in mind that you will still need to enforce that channel IDs are unique. A very effective approach is to include the team name as a prefix to avoid collisions. (ie. “red-general” and “blue-general” instead of just “general”)

By default the user search will only return results from teams that user is a part of. API injects filter {teams: {$in: ["red", "blue"]}} for every request that doesn’t already contain filter for teams field. If you want to query users from all teams, you have to provide empty filter like this: {teams:{}}. For server-side requests, this filter does not apply.

// Search for users with the name Jordan that are part of the red team
val filter = Filters.and(
  Filters.eq("name", "Jordan"),
  Filters.contains("teams", "red")
)

client.queryUsers(QueryUsersRequest(filter, offset = 0, limit = 1)).enqueue { result ->
  if (result.isSuccess) {
    val users: List<User> = result.data()
  } else {
    // Handle result.error()
  }
}

Users that cannot be displayed to the current user due to lack of permissions will be omitted from response.

Query Channels

When using multi-tenant, the query channels endpoint will only return channels that match the query and are on the same team as the user. API injects filter {team: {$in: [<user_teams>]}} for every request that doesn’t already contain filter for team field. If you want to query channels from all teams, you have to provide empty filter like this: {team:{}}. For server-side requests, this filter does not apply.

// query all channels from teams that user is a part of
await client.queryChannels({});

// query all channels from all teams
await client.queryChannels({ team: {} });

// query all channels with no teams
await client.queryChannels({ team: { $eq: null } });

In case if response contains channels that user cannot access, an access error will be returned.

Multi-Tenant Permissions

In tables below you will find default permission grants for builtin roles that designed for multi-tenant applications. They are useful for multi-tenant applications only.

By default, for multi-tenant applications, all objects (users, channels, and messages) must belong to the same team to be able to interact. These multi-tenant permissions enable overriding that behavior, so that certain users can have permissions to interact with objects on any team

Scope video:livestream

Permission ID

Scope video:development

Permission ID

Scope .app

Permission IDglobal_moderatorglobal_admin
flag-user-any-team
mute-user-any-team
read-flag-reports-any-team
search-user-any-team
update-flag-report-any-team
update-user-owner

Scope video:audio_room

Permission ID

Scope video:default

Permission ID

Scope messaging

Permission IDglobal_moderatorglobal_admin
add-links-any-team
ban-channel-member-any-team
ban-user-any-team
create-call-any-team
create-channel-any-team
create-message-any-team
create-attachment-any-team
create-mention-any-team
create-reaction-any-team
create-system-message-any-team
delete-attachment-any-team
delete-channel-any-team✖️
delete-channel-owner-any-team✖️
delete-message-any-team
delete-reaction-any-team
flag-message-any-team
join-call-any-team
mute-channel-any-team
pin-message-any-team
read-channel-any-team
read-channel-members-any-team
read-message-flags-any-team
recreate-channel-any-team✖️
recreate-channel-owner-any-team✖️
remove-own-channel-membership-any-team
run-message-action-any-team
send-custom-event-any-team
skip-channel-cooldown-any-team
skip-message-moderation-any-team
truncate-channel-any-team✖️
truncate-channel-owner-any-team✖️
unblock-message-any-team
update-channel-any-team
update-channel-cooldown-any-team
update-channel-frozen-any-team
update-channel-members-any-team
update-message-any-team
upload-attachment-any-team

Scope livestream

Permission IDglobal_moderatorglobal_admin
add-links-any-team
ban-channel-member-any-team
ban-user-any-team
create-call-any-team
create-channel-any-team
create-message-any-team
create-attachment-any-team
create-mention-any-team
create-reaction-any-team
create-system-message-any-team
delete-attachment-any-team
delete-channel-any-team✖️
delete-message-any-team
delete-reaction-any-team
flag-message-any-team
join-call-any-team
mute-channel-any-team
pin-message-any-team
read-channel-any-team
read-channel-members-any-team
read-message-flags-any-team
recreate-channel-any-team✖️
remove-own-channel-membership-any-team✖️
run-message-action-any-team
send-custom-event-any-team
skip-channel-cooldown-any-team
skip-message-moderation-any-team
truncate-channel-any-team✖️
unblock-message-any-team
update-channel-any-team✖️
update-channel-cooldown-any-team
update-channel-frozen-any-team
update-channel-members-any-team✖️
update-message-any-team
upload-attachment-any-team

Scope team

Permission IDglobal_moderatorglobal_admin
add-links-any-team
ban-channel-member-any-team
ban-user-any-team
create-call-any-team
create-channel-any-team
create-message-any-team
create-attachment-any-team
create-mention-any-team
create-reaction-any-team
create-system-message-any-team
delete-attachment-any-team
delete-channel-any-team✖️
delete-channel-owner-any-team✖️
delete-message-any-team
delete-reaction-any-team
flag-message-any-team
join-call-any-team
mute-channel-any-team
pin-message-any-team
read-channel-any-team
read-channel-members-any-team
read-message-flags-any-team
recreate-channel-any-team✖️
recreate-channel-owner-any-team✖️
remove-own-channel-membership-any-team
run-message-action-any-team
send-custom-event-any-team
skip-channel-cooldown-any-team
skip-message-moderation-any-team
truncate-channel-any-team✖️
truncate-channel-owner-any-team✖️
unblock-message-any-team
update-channel-any-team
update-channel-cooldown-any-team
update-channel-frozen-any-team
update-channel-members-any-team
update-message-any-team
upload-attachment-any-team

Scope commerce

Permission IDglobal_moderatorglobal_admin
add-links-any-team
ban-channel-member-any-team
ban-user-any-team
create-call-any-team
create-channel-any-team
create-message-any-team
create-attachment-any-team
create-mention-any-team
create-reaction-any-team
create-system-message-any-team
delete-attachment-any-team
delete-channel-any-team✖️
delete-message-any-team
delete-reaction-any-team
flag-message-any-team
join-call-any-team
mute-channel-any-team
pin-message-any-team
read-channel-any-team
read-channel-members-any-team
read-message-flags-any-team
recreate-channel-any-team✖️
remove-own-channel-membership-any-team
run-message-action-any-team
send-custom-event-any-team
skip-channel-cooldown-any-team
skip-message-moderation-any-team
truncate-channel-any-team✖️
unblock-message-any-team
update-channel-any-team
update-channel-cooldown-any-team
update-channel-frozen-any-team
update-channel-members-any-team
update-message-any-team
upload-attachment-any-team

Scope gaming

Permission IDglobal_moderatorglobal_admin
add-links-any-team
ban-channel-member-any-team
ban-user-any-team
create-call-any-team
create-channel-any-team✖️
create-message-any-team
create-attachment-any-team
create-mention-any-team
create-reaction-any-team
create-system-message-any-team
delete-attachment-any-team
delete-channel-any-team✖️
delete-message-any-team
delete-reaction-any-team
flag-message-any-team
join-call-any-team
mute-channel-any-team
pin-message-any-team
read-channel-any-team
read-channel-members-any-team
read-message-flags-any-team
recreate-channel-any-team✖️
remove-own-channel-membership-any-team
run-message-action-any-team
send-custom-event-any-team
skip-channel-cooldown-any-team
skip-message-moderation-any-team
truncate-channel-any-team✖️
unblock-message-any-team
update-channel-any-team✖️
update-channel-cooldown-any-team
update-channel-frozen-any-team
update-channel-members-any-team✖️
update-message-any-team
upload-attachment-any-team
© Getstream.io, Inc. All Rights Reserved.