Querying Channels

Last Edit: Jul 07 2020

If you’re building a similar application to Facebook Messenger or Intercom, you’ll want to show a list of Channels. The Chat API supports MongoDB style queries to make this easy to implement.

You can query channels based on built-in fields as well as any custom field you add to channels. Multiple filters can be combined using AND, OR logical operators, each filter can use its comparison (equality, inequality, greater than, greater or equal, etc.). You can find the complete list of supported operators in the query syntax section of the docs.

As an example, let's say that you want to query the last conversations I participated in sorted by last_message_at.

Stream Chat does not run MongoDB on the backend, only a subset of the query options are available.

Here’s an example of how you can query the list of channels:


const filter = { type: 'messaging', members: { $in: ['thierry'] } };
const sort = { last_message_at: -1 };

const channels = await authClient.queryChannels(filter, sort, {
    watch: true,
    state: true,
});

for (const c of channels) {
    console.log(c.custom.name, c.cid);
}
                    

final filter = {
  "type": "messaging",
  "members": {
    "\$in": ["thierry"]
  }
};

final sort = [SortOption("last_message_at", direction: SortOption.DESC)];

final response = await client.queryChannels(
  filter: filter,
  sort: sort,
  options: {
    "watch": true,
    "state": true,
  },
);

response.channels.forEach((ChannelState f) {
  print("${f.channel.extraData['name']} ${f.channel.cid}");
});
                    

$filter  = ['members' => ['$in' => ['elon', 'jack', 'jessie'] ]];
$sort    = ['last_message_at' => 1]; // sorting direction (1 or -1)
$options = ['limit' => 10];

$channels = $client->queryChannels($filter,$sort,$options);
                    

val filter = Filters.`in`("members", "thierry").put("type", "messaging")
val offset = 0
val limit = 10
val sort = QuerySort().desc("last_message_at")
val request = QueryChannelsRequest(filter, offset, limit, sort)
request.watch = true
request.state = true

client.queryChannels(request).enqueue {
    val channels = it.data()
}
                    

let query = ChannelsQuery(filter: .equal("type", to: "messaging") & .in("members", ["thierry"]),
                          sort: [.init("last_message_at", isAscending: false)],
                          pagination: [.limit(10)])
Client.shared.queryChannels(query: query) { (result) in
    // handle result
}
                    

Query Parameters

Name Type Description Default Optional
filters object The query filters to use. You can query on any of the custom fields you've defined on the Channel. You can also filter other built-in channel fields, see next section for reference. {}
sort objects The sorting uses for the channels matching the filters. Sorting is based on field and direction, multiple sorting options can be provided. You can sort based on last_updated, last_message_at, updated_at, created_at, member_count, unread_count or has_unread(unread status). Direction can be ascending (1) or descending (-1) {last_updated_at: -1}
options object Query options. See below. {}
By default when query channels does not have any filter and it will match all channels on your application. While this might be OK during development, you most likely want to have at least some basic filtering.
At a minumum, the filter should include members: { $in: [userID] } .

The query channels endpoint will only return channels that the user can read, you should make sure that the query uses a filter that includes such logic. For example: messaging channels are readable only to their members, such requirement can be included in the query filter (see below).

Common filters by use-case

Messaging and Team

On messaging and team applications you normally have users added to channels as a member. A good starting point is to use this filter to show the channels the user is participating.


const filter = { members: { $in: ['thierry'] } };
                    

final filter = {
  "type": "messaging",
  "members": {
    "\$in": ["thierry"],
  }
};
                    

$filter  = ['members' => ['$in' => ['thierry'] ]];
                    

val filter = Filters
        .`in`("members", "thierry")
        .put("type", "messaging")
                    

let filter = Filter.currentUserInMembers
// or
let filter = Filter.in("members", [User.current.id])
// or
let filter = Filter.in("members" ["thierry"])
                    

Support

On a support chat, you probably want to attach additional information to channels such as the support agent handling the case and other information regarding the status of the support case (ie. open, pending, solved).


const filter = { agent_id: `${user.Id}`, status: { $in: ['pending', 'open', 'new'] }};
                    

final filter = {
  "agent_id": "${user.id}",
  "status": {
    "\$in": ['pending', 'open', 'new'],
  }
};
                    

$filter  = ['agent_id' => 'agent-id', 'status' => ['$in' => ['pending', 'open', 'new'] ]];
                    

val filter = Filters
    .`in`("status", "pending", "open", "new")
    .put("agent_id", userId)
                    

let filter = Filter.equal("agent_id", to: User.current.id) & .in("status", ["pending", "open", "new"])
                    

Channel Queryable Built-In Fields

The following channel fields can be used to filter your query results

Name Type Description Example
frozen boolean channel frozen status false
type string the type of channel messaging
id string the ID of the channel general
cid string the full channel ID messaging:general
members string or list of string the list of user IDs of the channel members marcelo or [thierry, marcelo]
invite string, must be one of these values: (pending, accepted, rejected) the status of the invite pending

Query Options

Name Type Description Default Optional
state boolean if true returns the Channel state true
watch boolean if true listen to changes to this Channel in real time. false
limit integer The number of channels to return (max is 30) 10
offset integer The offset (max is 1000) 0
message_limit integer How many messages should be included to each channel 25

Query channels allows you to retrieve channels and start watching them at same time using the watch parameter set to true.

Response

The result of querying a channel is a list of ChannelState objects which include all the required information to render them without any additional API call.

ChannelState Response

Field Name Description
channel The data for this channel
messages The most recent messages for this channel (see message_limit option)
watcher_count How many users are currently watching the channel
read The read state for all members of the channel
members The list of members, up to 100 ordered by the most recent added

The event system ensures that your local copy of the channel state stays up to date.

Although the watcher count can be obtained through this method, a list of watchers cannot be. For that, you will need to use channel.query( { watchers: { limit: 20, offset: 0 } });. See more here.
Channel state is immutable. It leverages seamless-immutable to prevent bugs caused by accidentally editing state.

Query channel requests can be paginated similar to how you paginate on other calls. Here's a short example:


// retrieve 20 channels with Thierry as a member and skip first 10

const filter = { members: { $in: ['thierry'] } };
const sort = { last_message_at: -1 };

const channels = await authClient.queryChannels(filter, sort, {
    limit: 20, offset: 10
});
                    

// retrieve 20 channels with Thierry as a member and skip first 10

final filter = {
  "members": {
    "\$in": ['thierry'],
  }
};

final sort = [SortOption("last_message_at", direction: SortOption.DESC)];

final response = await client.queryChannels(
  filter: filter,
  sort: sort,
  options: {
    "limit": 20,
    "offset": 10,
  },
);

response.channels.forEach((ChannelState f) {
  print("${f.channel.extraData['name']} ${f.channel.cid}");
});
                    

// retrieve 20 channels with Thierry as a member and skip first 10
$filter  = ['members' => ['$in' => ['thierry'] ]];
$sort    = ['last_message_at' => -1]; // sorting direction (1 or -1)
$options = ['limit' => 20, 'offset' => 10];

$channels = $client->queryChannels($filter,$sort,$options);
                    

val filter = Filters.`in`("members", "thierry")
val offset = 0
val limit = 10
val request = QueryChannelsRequest(filter, offset, limit)

client.queryChannels(request).enqueue {
    val channels = it.data()
}
                    

let channel = Client.shared.channel(type: .messaging, id: "general")

let query = ChannelQuery(channel: channel,
                         messagesPagination: [.limit(2), .lessThanOrEqual("123")],
                         membersPagination: [.limit(2)],
                         watchersPagination: [.limit(2)])
    
Client.shared.queryChannel(query: query) { (result) in
    // handle result
}

// or

channel.query(messagesPagination: [.limit(2), .lessThanOrEqual("123")],
              membersPagination: [.limit(2)],
              watchersPagination: [.limit(2)]) { result in /* Handle result */ }