# Querying Channels

Channel lists are a core part of most messaging applications, and our SDKs make them easy to build using the Channel List components. These lists are powered by the Query Channels API, which retrieves channels based on filter criteria, sorting options, and pagination settings.

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

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val request = QueryChannelsRequest(
  filter = Filters.and(
    Filters.eq("type", "messaging"),
    Filters.`in`("members", listOf("thierry")),
  ),
  offset = 0,
  limit = 10,
  querySort = QuerySortByField.descByName("lastMessageAt")
).apply {
  watch = true
  state = true
}

client.queryChannels(request).enqueue { result ->
  if (result is Result.Success) {
    val channels: List<Channel> = result.value
  } else {
    // Handle Result.Failure
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
const filter = { type: "messaging", members: { $in: ["thierry"] } };
const sort = [{ last_message_at: -1 }];
const options = { limit: 15 };

const channels = await chatClient.queryChannels(filter, sort, options);
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
final filter = Filter.in_('members',['thierry']);

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

final channels = await client.queryChannels(
 filter: filter,
 sort: sort,
 watch: true,
 state: true,
).last;

channels.forEach((Channel c) {
 print("${c.extraData['name']} ${c.cid}");
});
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\ChatClient;
use GetStream\GeneratedModels as Models;

$response = $client->queryChannels(new Models\QueryChannelsRequest(
    filterConditions: (object)["members" => (object)['$in' => ["elon", "jack", "jessie"]]],
    sort: [new Models\SortParamRequest(field: "last_message_at", direction: 1)],
    limit: 10,
));
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let controller = chatClient.channelListController(
  query: .init(
    filter: .and([.equal(.type, to: .messaging), .containMembers(userIds: ["thierry"])]),
    sort: [.init(key: .lastMessageAt, isAscending: false)],
    pageSize: 10
  )
)

controller.synchronize { error in
  if let error = error {
    // handle error
    print(error)
  } else {
    // access channels
    print(controller.channels)

    // load more if needed
    controller.loadNextChannels(limit: 10) { error in
      // handle error / access channels
    }
  }
}
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
const FFilter Filter = FFilter::And({
  FFilter::In(TEXT("members"), {TEXT("thierry")}),
  FFilter::Equal(TEXT("type"), TEXT("messaging")),
});
const TArray<FChannelSortOption> SortOptions{{EChannelSortField::LastMessageAt, ESortDirection::Descending}};
Client->QueryChannels(
  Filter,
  SortOptions,
  EChannelFlags::State,
  {},  // Pagination options
  [](const TArray<UChatChannel*> ReceivedChannels)
  {
    // Do something with ReceivedChannels
  });
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
resp, err := client.Chat().QueryChannels(ctx, &getstream.QueryChannelsRequest{
	FilterConditions: map[string]any{
		"members": map[string]any{
			"$in": []string{"elon", "jack", "jessie"},
		},
	},
	Sort:  []getstream.SortParamRequest{{Field: getstream.PtrTo("last_message_at"), Direction: getstream.PtrTo(1)}},
	Limit: getstream.PtrTo(10),
})
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import SortParamRequest

client.chat.query_channels(
    filter_conditions={"members": {"$in": ["elon", "jack", "jessie"]}},
    sort=[SortParamRequest(field="last_message_at", direction=1)],
    limit=10,
)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.query_channels(
  Models::QueryChannelsRequest.new(
    filter_conditions: { 'members' => { '$in' => ['elon', 'jack', 'jessie'] } },
    sort: [Models::SortParamRequest.new(field: 'last_message_at', direction: 1)],
    limit: 10
  )
)
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
using GetStream;
using GetStream.Models;

var client = new StreamClient("{{ api_key }}", "{{ api_secret }}");
var chat = new ChatClient(client);

var resp = await chat.QueryChannelsAsync(new QueryChannelsRequest
{
    FilterConditions = new Dictionary<string, object>
    {
        ["members"] = new Dictionary<string, object> { ["$in"] = new[] { "elon", "jack", "jessie" } }
    },
    Sort = new List<SortParamRequest>
    {
        new SortParamRequest { Field = "last_message_at", Direction = 1 }
    },
    Limit = 10
});
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
var filters = new List<IFieldFilterRule>
{
  // Return only channels where local user is a member
  ChannelFilter.Members.In(Client.LocalUserData.UserId),
};

var channels = await Client.QueryChannelsAsync(filters);
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK
FilterObject filter = Filters.and(
    Filters.eq("type", "messaging"),
    Filters.in("members", Arrays.asList("thierry"))
);
int offset = 0;
int limit = 10;
QuerySortByField<Channel> sort = QuerySortByField.descByName("last_message_at");
int messageLimit = 0;
int memberLimit = 0;

QueryChannelsRequest request = new QueryChannelsRequest(filter, offset, limit, sort, messageLimit, memberLimit)
    .withWatch()
    .withState();

client.queryChannels(request).enqueue(result -> {
  if (result.isSuccess()) {
    List<Channel> channels = result.data();
  } else {
    // Handle result.error()
  }
});

// Backend SDK
chat.queryChannels(QueryChannelsRequest.builder()
    .filterConditions(Map.of(
        "type", "messaging",
        "members", Map.of("$in", List.of("thierry"))))
    .sort(List.of(SortParamRequest.builder().field("last_message_at").direction(-1).build()))
    .state(true)
    .build()).execute();
```

</codetabs-item>

</codetabs>

## Query Parameters

| Name    | Type                       | Description                                                                                                                                                                                                                                                                            | Default              | Optional |
| ------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -------- |
| filters | object                     | Filter criteria for channel fields. See [Queryable Fields](#channel-queryable-built-in-fields) for available options.                                                                                                                                                                  | {}                   |          |
| sort    | object or array of objects | Sorting criteria based on field and direction. You can sort by **last_updated**, **last_message_at**, **updated_at**, **created_at**, **member_count**, **unread_count**, or **has_unread**. Direction can be ascending (1) or descending (-1). Multiple sort options can be provided. | [{last_updated: -1}] |          |
| options | object                     | Additional query options. See [Query Options](#query-options).                                                                                                                                                                                                                         | {}                   |          |

<admonition type="info">

An empty filter matches all channels in your application. In production, always include at least `members: { $in: [userID] }` to return only channels the user belongs to.

</admonition>

The API only returns channels that the user has permission to read. For messaging channels, this typically means the user must be a member. Include appropriate filters to match your channel type's permission model.

## Common Filters

Understanding which filters perform well at scale helps you build efficient channel queries. This section covers common filter patterns with their performance characteristics.

<admonition type="tip">

**Performance Summary**: Filters using indexed fields (`cid`, `type`, `members`, `last_message_at`) perform best. See [Performance Considerations](#performance-considerations) for detailed guidance.

</admonition>

### Messaging and Team Channels

For most messaging applications, filter by channel type and membership. This pattern uses indexed fields and performs well at scale.

<admonition type="warning">

**High membership counts**: For users with a large number of channel memberships (more than a few thousand), filtering by `members: { $in: [userID] }` becomes less selective and may cause performance issues. In these cases, consider adding additional filters (like `last_message_at`) to narrow the result set.

</admonition>

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val filter = Filters.and(
  Filters.eq("type", "messaging"),
  Filters.`in`("members", listOf("thierry")),
)
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
const filter = { members: { $in: ["thierry"] }, type: "messaging" };
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
final filter = Filter.and([
  Filter.equal('type', 'messaging'),
  Filter.in_('members', ['thierry']),
]);
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$filter = (object)["members" => (object)['$in' => ["thierry"]], "type" => "messaging"];
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let currentUserChannels = chatClient.channelListController(
  query: .init(
    filter: .and([
      .equal(.type, to: .messaging),
      .containMembers(userIds: [chatClient.currentUserId!])
    ])
  )
)

currentUserChannels.synchronize()
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
const FFilter Filter = FFilter::And({
  FFilter::Equal(TEXT("type"), TEXT("messaging")),
  FFilter::In(TEXT("members"), {TEXT("thierry")}),
});
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
filter := map[string]any{
	"type": "messaging",
	"members": map[string]any{
		"$in": []string{"thierry"},
	},
}
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
filter = {"type": "messaging", "members": {"$in": ["thierry"]}}
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'

filter = { 'type' => 'messaging', 'members' => { '$in' => ['thierry'] } }
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
var filter = new Dictionary<string, object>
{
    ["type"] = "messaging",
    ["members"] = new Dictionary<string, object> { ["$in"] = new[] { "thierry" } }
};
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK
FilterObject filter = Filters.and(
  Filters.eq("type", "messaging"),
  Filters.in("members", Arrays.asList("thierry"))
);

// Backend SDK
Map<String, Object> filter = Map.of(
    "type", "messaging",
    "members", Map.of("$in", List.of("thierry")));
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
var filters = new List<IFieldFilterRule>
{
  ChannelFilter.Type.EqualsTo(ChannelType.Messaging),
  ChannelFilter.Members.In(Client.LocalUserData.UserId),
};

var channels = await Client.QueryChannelsAsync(filters);
```

</codetabs-item>

</codetabs>

## Channel Queryable Built-In Fields

The following fields can be used in your filter criteria:

| Name             | Type                                 | Description                                                                    | Supported Operators                | Example                 |
| ---------------- | ------------------------------------ | ------------------------------------------------------------------------------ | ---------------------------------- | ----------------------- |
| frozen           | boolean                              | Channel frozen status                                                          | $eq                                | false                   |
| type             | string or list of string             | Channel type                                                                   | $in, $eq                           | messaging               |
| cid              | string or list of string             | Full channel ID (type:id)                                                      | $in, $eq                           | messaging:general       |
| members          | list of string                       | User IDs of channel members                                                    | $in                                | [thierry, marcelo]      |
| invite           | string (pending, accepted, rejected) | Invite status                                                                  | $eq                                | pending                 |
| joined           | boolean                              | Whether the current user has joined the channel                                | $eq                                | true                    |
| muted            | boolean                              | Whether the current user has muted the channel                                 | $eq                                | true                    |
| member.user.name | string                               | Name property of a channel member                                              | $autocomplete, $eq                 | marc                    |
| created_by_id    | string                               | ID of the user who created the channel                                         | $eq                                | marcelo                 |
| hidden           | boolean                              | Whether the current user has hidden the channel                                | $eq                                | false                   |
| last_message_at  | string (RFC3339 timestamp)           | Time of the last message                                                       | $eq, $gt, $lt, $gte, $lte, $exists | 2021-01-15T09:30:20.45Z |
| member_count     | integer                              | Number of members                                                              | $eq, $gt, $lt, $gte, $lte          | 5                       |
| created_at       | string (RFC3339 timestamp)           | Channel creation time                                                          | $eq, $gt, $lt, $gte, $lte, $exists | 2021-01-15T09:30:20.45Z |
| updated_at       | string (RFC3339 timestamp)           | Channel update time                                                            | $eq, $gt, $lt, $gte, $lte          | 2021-01-15T09:30:20.45Z |
| team             | string                               | Team associated with the channel                                               | $eq                                | stream                  |
| last_updated     | string (RFC3339 timestamp)           | Time of last message, or channel creation time if no messages exist            | $eq, $gt, $lt, $gte, $lte          | 2021-01-15T09:30:20.45Z |
| disabled         | boolean                              | Channel disabled status                                                        | $eq                                | false                   |
| has_unread       | boolean                              | Whether the user has unread messages (only `true` supported, max 100 channels) | true                               | true                    |
| app_banned       | string                               | Filter by application-banned users (only for 2-member channels)                | excluded, only                     | excluded                |

<admonition type="info">

For supported query operators, see [Query Syntax Operators](/chat/docs/<framework>/query_syntax_operators/).

</admonition>

<admonition type="info">

The `app_banned` filter only works on direct message channels with exactly 2 members.

</admonition>

## Query Options

| Name          | Type    | Description                                          | Default | Optional |
| ------------- | ------- | ---------------------------------------------------- | ------- | -------- |
| state         | boolean | Return channel state                                 | true    | ✓        |
| watch         | boolean | Subscribe to real-time updates for returned channels | true    | ✓        |
| limit         | integer | Number of channels to return (max 30)                | 10      | ✓        |
| offset        | integer | Number of channels to skip (max 1000)                | 0       | ✓        |
| message_limit | integer | Messages to include per channel (max 300)            | 25      | ✓        |
| member_limit  | integer | Members to include per channel (max 100)             | 100     | ✓        |

<admonition type="tip">

**Performance Tip**: Setting `state: false` and `watch: false` reduces response size and processing time. Use these options when you only need channel IDs or basic metadata—for example, during background syncs, administrative operations, or when building lightweight channel lists that don't require full state.

</admonition>

## Response

The API returns a list of `ChannelState` objects containing all information needed to render channels without additional API calls.

### ChannelState Fields

| Field Name      | Description                                                                                                     |
| --------------- | --------------------------------------------------------------------------------------------------------------- |
| channel         | Channel data                                                                                                    |
| messages        | Recent messages (based on message_limit)                                                                        |
| watcher_count   | Number of users currently watching                                                                              |
| read            | Read state for up to 100 members, ordered by most recently added (current user's read state is always included) |
| members         | Up to 100 members, ordered by most recently added                                                               |
| pinned_messages | Up to 10 most recent pinned messages                                                                            |

<disclosure label="Example Response">

```json
[
  {
    "id": "f8IOxxbt",
    "type": "messaging",
    "cid": "messaging:f8IOxxbt",
    "last_message_at": "2020-01-10T07:26:46.791232Z",
    "created_at": "2020-01-10T07:25:37.63256Z",
    "updated_at": "2020-01-10T07:25:37.632561Z",
    "created_by": {
      "id": "8ce4c6e11118ca103a0a7c633dcf60dd",
      "role": "admin",
      "created_at": "2019-08-27T17:33:14.442265Z",
      "updated_at": "2020-01-10T07:25:36.402819Z",
      "last_active": "2020-01-10T07:25:36.395796Z",
      "banned": false,
      "online": false,
      "image": "https://ui-avatars.com/api/?name=mezie&size=192&background=000000&color=6E7FFE&length=1",
      "name": "mezie",
      "username": "mezie"
    },
    "frozen": false,
    "config": {
      "created_at": "2020-01-20T10:23:44.878185331Z",
      "updated_at": "2020-01-20T10:23:44.878185458Z",
      "name": "messaging",
      "typing_events": true,
      "read_events": true,
      "connect_events": true,
      "search": true,
      "reactions": true,
      "replies": true,
      "mutes": true,
      "uploads": true,
      "url_enrichment": true,
      "max_message_length": 5000,
      "automod": "disabled",
      "automod_behavior": "flag",
      "commands": [
        {
          "name": "giphy",
          "description": "Post a random gif to the channel",
          "args": "[text]",
          "set": "fun_set"
        }
      ]
    },
    "name": "Video Call"
  }
]
```

</disclosure>

## Pagination

Use `limit` and `offset` to paginate through results:

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// Get the first 10 channels
val filter = Filters.`in`("members", "thierry")
val offset = 0
val limit = 10
val request = QueryChannelsRequest(filter, offset, limit)
client.queryChannels(request).enqueue { result ->
  if (result is Result.Success) {
    val channels = result.value
  } else {
    // Handle Result.Failure
  }
}

// Get the second 10 channels
val nextRequest = QueryChannelsRequest(
  filter = filter,
  offset = 10, // Skips first 10
  limit = limit
)
client.queryChannels(nextRequest).enqueue { result ->
  if (result is Result.Success) {
    val channels = result.value
  } else {
    // Handle Result.Failure
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

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

// Get channels 11-30
const channels = await authClient.queryChannels(filter, sort, {
  limit: 20,
  offset: 10,
});
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
final filter = Filter.in_('members', ['thierry']);

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

final response = await client.queryChannels(
 filter: filter,
 sort: sort,
 paginationParams: PaginationParams(
  limit: 20,
  offset: 10,
 ),
);

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

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\ChatClient;
use GetStream\GeneratedModels as Models;

$response = $client->queryChannels(new Models\QueryChannelsRequest(
    filterConditions: (object)["members" => (object)['$in' => ["thierry"]]],
    sort: [new Models\SortParamRequest(field: "last_message_at", direction: -1)],
    limit: 20,
    offset: 10,
));
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let controller = chatClient.channelListController(
  query: .init(
    filter: .containMembers(userIds: ["thierry"]),
    pageSize: 10
  )
)

// Get the first 10 channels
controller.synchronize { error in
  if let error = error {
    // handle error
    print(error)
  } else {
    // Access channels
    print(controller.channels)

    // Get the next 10 channels
    controller.loadNextChannels { error in
      // handle error / access channels
      print(error ?? controller.channels)
    }
  }
}
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
const FFilter Filter = FFilter::In(TEXT("members"), {TEXT("thierry")});
const TArray<FChannelSortOption> SortOptions{
  {EChannelSortField::LastMessageAt, ESortDirection::Descending},
};
const FPaginationOptions PaginationOptions{
  20,  // Limit
  10,  // Offset
};
Client->QueryChannels(
  Filter,
  SortOptions,
  EChannelFlags::State,
  PaginationOptions,
  [](const TArray<UChatChannel*> ReceivedChannels)
  {
    // Started watching channels
  });
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
resp, err := client.Chat().QueryChannels(ctx, &getstream.QueryChannelsRequest{
	FilterConditions: map[string]any{
		"members": map[string]any{
			"$in": []string{"thierry"},
		},
	},
	Sort:   []getstream.SortParamRequest{{Field: getstream.PtrTo("last_message_at"), Direction: getstream.PtrTo(-1)}},
	Limit:  getstream.PtrTo(20),
	Offset: getstream.PtrTo(10),
})
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import SortParamRequest

client.chat.query_channels(
    filter_conditions={"members": {"$in": ["thierry"]}},
    sort=[SortParamRequest(field="last_message_at", direction=-1)],
    limit=20,
    offset=10,
)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.query_channels(
  Models::QueryChannelsRequest.new(
    filter_conditions: { 'members' => { '$in' => ['thierry'] } },
    sort: [Models::SortParamRequest.new(field: 'last_message_at', direction: -1)],
    limit: 20,
    offset: 10
  )
)
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
var resp = await chat.QueryChannelsAsync(new QueryChannelsRequest
{
    FilterConditions = new Dictionary<string, object>
    {
        ["members"] = new Dictionary<string, object> { ["$in"] = new[] { "thierry" } }
    },
    Sort = new List<SortParamRequest>
    {
        new SortParamRequest { Field = "last_message_at", Direction = -1 }
    },
    Limit = 20,
    Offset = 10
});
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK

// Get the first 10 channels
FilterObject filter = Filters.in("members", "thierry");
int offset = 0;
int limit = 10;
QuerySorter<Channel> sort = new QuerySortByField<>();
int messageLimit = 0;
int memberLimit = 0;
QueryChannelsRequest request = new QueryChannelsRequest(filter, offset, limit, sort, messageLimit, memberLimit);

client.queryChannels(request).enqueue(result -> {
  if (result.isSuccess()) {
    List<Channel> channels = result.data();
  } else {
    // Handle result.error()
  }
});

// Get the second 10 channels
int nextOffset = 10; // Skips first 10
QueryChannelsRequest nextRequest = new QueryChannelsRequest(filter, nextOffset, limit, sort, messageLimit, memberLimit);
client.queryChannels(nextRequest).enqueue(result -> {
  if (result.isSuccess()) {
    List<Channel> channels = result.data();
  } else {
    // Handle result.error()
  }
});

// Backend SDK
// Get first 10 channels
Map<String, Object> filter = Map.of("members", Map.of("$in", List.of("thierry")));

var channels = chat.queryChannels(QueryChannelsRequest.builder()
    .filterConditions(filter)
    .state(true)
    .limit(10)
    .offset(0)
    .build()).execute();

// Get the second 10 channels
var nextChannels = chat.queryChannels(QueryChannelsRequest.builder()
    .filterConditions(filter)
    .state(true)
    .limit(10)
    .offset(10)
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
var filters = new List<IFieldFilterRule>
{
  // Return only channels where local user is a member
  ChannelFilter.Members.In(Client.LocalUserData.UserId),
};

// Pass limit and offset to control pagination
// Limit - records per page
// Offset - records to skip
var channels = await Client.QueryChannelsAsync(filters, limit: 30, offset: 60);
```

</codetabs-item>

</codetabs>

<admonition type="info">

Always include `members: { $in: [userID] }` in your filter to ensure consistent pagination results. Without this filter, channel list changes may cause pagination issues.

</admonition>

## Best Practices

### Channel Creation and Watching

A channel is not created in the API until one of the following methods is called. Each method has subtle differences:

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
channel.create();
channel.query();
channel.watch();
```

</codetabs-item>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
channelClient.create(memberIds = listOf("user-1", "user-2"), extraData = emptyMap()).enqueue()
channelClient.query(QueryChannelRequest()).enqueue()
channelClient.watch().enqueue()
```

</codetabs-item>

</codetabs>

Only one of these is necessary. For example, calling `watch` automatically creates the channel in addition to subscribing to real-time updates—there's no need to call `create` separately.

With `queryChannels`, a user can watch up to 30 channels in a single API call. This eliminates the need to watch channels individually using `channel.watch()` after querying. Using `queryChannels` can substantially decrease API calls, reducing network traffic and improving performance when working with many channels.

### Filter Best Practices

Channel lists often form the backbone of the chat experience and are typically one of the first views users see. Use the most selective filter possible:

- **Filter by CID** is the most performant query you can use
- **For social messaging** (DMs, group chats), use at minimum `type` and `members: { $in: [userID] }`
- **Avoid overly complex queries** with more than one AND or OR statement
- **Filtering by type alone** is not recommended—always include additional criteria
- **Use Predefined Filters** in production for frequently used query patterns

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// Most performant: Filter by CID
const filter = { cid: channelCID };

// Recommended for social messaging
const filter = { type: "messaging", members: { $in: [userID] } };

// Not recommended: type alone
const filter = { type: "messaging" };
```

</codetabs-item>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// Most performant: Filter by CID
val filter = Filters.eq("cid", channelCid)

// Recommended for social messaging
val filter = Filters.and(
    Filters.eq("type", "messaging"),
    Filters.`in`("members", listOf(userId)),
)

// Not recommended: type alone
val filter = Filters.eq("type", "messaging")
```

</codetabs-item>

</codetabs>

<admonition type="tip">

If your filter returns more than a few thousand channels, consider adding more selective criteria. For frequently used query patterns, use [Predefined Filters](#predefined-filters) to enable performance monitoring through the Dashboard. [Contact support](https://getstream.io/contact/support/) if you plan on having millions of channels and need guidance on optimal filters.

</admonition>

### Sort Best Practices

Always specify a sort parameter in your query. The default is `last_updated` (the more recent of `created_at` and `last_message_at`).

The most optimized sort options are:

- `last_updated` (default)
- `last_message_at`

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
const sort = { last_message_at: -1 };
```

</codetabs-item>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val sort = QuerySortByField.descByName<Channel>("last_message_at")
```

</codetabs-item>

</codetabs>

For the full list of supported query operators, see [Query Syntax Operators](/chat/docs/<framework>/query_syntax_operators/).

### Recommended Query Patterns

Following recommended patterns helps ensure your queries perform well as your application scales. Here are examples of good and bad query patterns for all server-side SDKs.

#### Good Pattern: Selective Filter with Indexed Fields

Use indexed fields like `type`, `members`, and `last_message_at` for efficient queries:

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// ✅ GOOD: Selective filter using indexed fields
const filter = {
  type: "messaging",
  members: { $in: [userId] },
  last_message_at: { $gte: thirtyDaysAgo },
};
const sort = { last_message_at: -1 };

const channels = await serverClient.queryChannels(filter, sort, { limit: 20 });
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// ✅ GOOD: Selective filter using indexed fields
filter := map[string]any{
	"type": "messaging",
	"members": map[string]any{
		"$in": []string{userID},
	},
	"last_message_at": map[string]any{
		"$gte": thirtyDaysAgo,
	},
}

resp, err := client.Chat().QueryChannels(ctx, &getstream.QueryChannelsRequest{
	FilterConditions: filter,
	Sort:             []getstream.SortParamRequest{{Field: getstream.PtrTo("last_message_at"), Direction: getstream.PtrTo(-1)}},
	Limit:            getstream.PtrTo(20),
})
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import SortParamRequest

# ✅ GOOD: Selective filter using indexed fields
channels = client.chat.query_channels(
    filter_conditions={
        "type": "messaging",
        "members": {"$in": [user_id]},
        "last_message_at": {"$gte": thirty_days_ago},
    },
    sort=[SortParamRequest(field="last_message_at", direction=-1)],
    limit=20,
)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# ✅ GOOD: Selective filter using indexed fields
filter = {
  'type' => 'messaging',
  'members' => { '$in' => [user_id] },
  'last_message_at' => { '$gte' => thirty_days_ago }
}

channels = client.chat.query_channels(
  Models::QueryChannelsRequest.new(
    filter_conditions: filter,
    sort: [Models::SortParamRequest.new(field: 'last_message_at', direction: -1)],
    limit: 20
  )
)
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\ChatClient;
use GetStream\GeneratedModels as Models;

// ✅ GOOD: Selective filter using indexed fields
$response = $client->queryChannels(new Models\QueryChannelsRequest(
    filterConditions: (object)[
        "type" => "messaging",
        "members" => (object)['$in' => [$userId]],
        "last_message_at" => (object)['$gte' => $thirtyDaysAgo],
    ],
    sort: [new Models\SortParamRequest(field: "last_message_at", direction: -1)],
    limit: 20,
));
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Backend SDK
// ✅ GOOD: Selective filter using indexed fields
Map<String, Object> filter = Map.of(
    "type", "messaging",
    "members", Map.of("$in", List.of(userId)),
    "last_message_at", Map.of("$gte", thirtyDaysAgo));

chat.queryChannels(QueryChannelsRequest.builder()
    .filterConditions(filter)
    .sort(List.of(SortParamRequest.builder().field("last_message_at").direction(-1).build()))
    .limit(20)
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// ✅ GOOD: Selective filter using indexed fields
var resp = await chat.QueryChannelsAsync(new QueryChannelsRequest
{
    FilterConditions = new Dictionary<string, object>
    {
        ["type"] = "messaging",
        ["members"] = new Dictionary<string, object> { ["$in"] = new[] { userId } },
        ["last_message_at"] = new Dictionary<string, object> { ["$gte"] = thirtyDaysAgo }
    },
    Sort = new List<SortParamRequest>
    {
        new SortParamRequest { Field = "last_message_at", Direction = -1 }
    },
    Limit = 20
});
```

</codetabs-item>

</codetabs>

#### Bad Pattern: Overly Broad or Complex Filters

Avoid overly broad filters or deep nesting of logical operators, which can cause performance issues at scale and may result in dynamic rate limiting:

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// ❌ BAD: Type-only filter (too broad)
const broadFilter = { type: "messaging" };

// ❌ BAD: Deep nesting of logical operators
const nestedFilter = {
  $and: [
    {
      $or: [{ frozen: true }, { disabled: true }],
    },
    {
      $or: [{ hidden: true }, { muted: true }],
    },
  ],
};
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// ❌ BAD: Type-only filter (too broad)
broadFilter := map[string]any{
	"type": "messaging",
}

// ❌ BAD: Deep nesting of logical operators
nestedFilter := map[string]any{
	"$and": []map[string]any{
		{
			"$or": []map[string]any{
				{"frozen": true},
				{"disabled": true},
			},
		},
		{
			"$or": []map[string]any{
				{"hidden": true},
				{"muted": true},
			},
		},
	},
}
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# ❌ BAD: Type-only filter (too broad)
broad_filter = {"type": "messaging"}

# ❌ BAD: Deep nesting of logical operators
nested_filter = {
    "$and": [
        {
            "$or": [
                {"frozen": True},
                {"disabled": True},
            ]
        },
        {
            "$or": [
                {"hidden": True},
                {"muted": True},
            ]
        },
    ]
}
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'

# ❌ BAD: Type-only filter (too broad)
broad_filter = { 'type' => 'messaging' }

# ❌ BAD: Deep nesting of logical operators
nested_filter = {
  '$and' => [
    {
      '$or' => [
        { 'frozen' => true },
        { 'disabled' => true }
      ]
    },
    {
      '$or' => [
        { 'hidden' => true },
        { 'muted' => true }
      ]
    }
  ]
}
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
// ❌ BAD: Type-only filter (too broad)
$broadFilter = (object)["type" => "messaging"];

// ❌ BAD: Deep nesting of logical operators
$nestedFilter = (object)[
    '$and' => [
        (object)[
            '$or' => [
                (object)["frozen" => true],
                (object)["disabled" => true],
            ],
        ],
        (object)[
            '$or' => [
                (object)["hidden" => true],
                (object)["muted" => true],
            ],
        ],
    ],
];
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Backend SDK
// ❌ BAD: Type-only filter (too broad)
Map<String, Object> broadFilter = Map.of("type", "messaging");

// ❌ BAD: Deep nesting of logical operators
Map<String, Object> nestedFilter = Map.of(
    "$and", List.of(
        Map.of("$or", List.of(
            Map.of("frozen", true),
            Map.of("disabled", true))),
        Map.of("$or", List.of(
            Map.of("hidden", true),
            Map.of("muted", true)))));
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// ❌ BAD: Type-only filter (too broad)
var broadFilter = new Dictionary<string, object>
{
    ["type"] = "messaging"
};

// ❌ BAD: Deep nesting of logical operators
var nestedFilter = new Dictionary<string, object>
{
    ["$and"] = new[]
    {
        new Dictionary<string, object>
        {
            ["$or"] = new[]
            {
                new Dictionary<string, object> { ["frozen"] = true },
                new Dictionary<string, object> { ["disabled"] = true }
            }
        },
        new Dictionary<string, object>
        {
            ["$or"] = new[]
            {
                new Dictionary<string, object> { ["hidden"] = true },
                new Dictionary<string, object> { ["muted"] = true }
            }
        }
    }
};
```

</codetabs-item>

</codetabs>

### Using Predefined Filters in Production

For frequently used query patterns, use [Predefined Filters](#predefined-filters) in production. They provide several benefits:

- **Consistency**: Define filter logic once and reuse across your application
- **Performance Monitoring**: View performance analysis through the Dashboard
- **Optimization Insights**: Receive recommendations for improving slow queries

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// Production-ready: Use Predefined Filter
const channels = await serverClient.queryChannels(
  {}, // filter_conditions ignored with predefined_filter
  { last_message_at: -1 },
  {
    predefined_filter: "user_messaging_channels",
    filter_values: { user_id: userId },
    limit: 20,
  },
);
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// Production-ready: Use Predefined Filter
resp, err := client.Chat().QueryChannels(ctx, &getstream.QueryChannelsRequest{
	PredefinedFilter: getstream.PtrTo("user_messaging_channels"),
	FilterValues: map[string]any{
		"user_id": userID,
	},
	Sort:  []getstream.SortParamRequest{{Field: getstream.PtrTo("last_message_at"), Direction: getstream.PtrTo(-1)}},
	Limit: getstream.PtrTo(20),
})
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import SortParamRequest

# Production-ready: Use Predefined Filter
channels = client.chat.query_channels(
    predefined_filter="user_messaging_channels",
    filter_values={"user_id": user_id},
    sort=[SortParamRequest(field="last_message_at", direction=-1)],
    limit=20,
)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Production-ready: Use Predefined Filter
channels = client.chat.query_channels(
  Models::QueryChannelsRequest.new(
    predefined_filter: 'user_messaging_channels',
    filter_values: { 'user_id' => user_id },
    sort: [Models::SortParamRequest.new(field: 'last_message_at', direction: -1)],
    limit: 20
  )
)
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\ChatClient;
use GetStream\GeneratedModels as Models;

// Production-ready: Use Predefined Filter
$response = $client->queryChannels(new Models\QueryChannelsRequest(
    predefinedFilter: "user_messaging_channels",
    filterValues: (object)["user_id" => $userId],
    sort: [new Models\SortParamRequest(field: "last_message_at", direction: -1)],
    limit: 20,
));
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Backend SDK
// Production-ready: Use Predefined Filter
chat.queryChannels(QueryChannelsRequest.builder()
    .predefinedFilter("user_messaging_channels")
    .filterValues(Map.of("user_id", userId))
    .sort(List.of(SortParamRequest.builder().field("last_message_at").direction(-1).build()))
    .limit(20)
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// Production-ready: Use Predefined Filter
var resp = await chat.QueryChannelsAsync(new QueryChannelsRequest
{
    PredefinedFilter = "user_messaging_channels",
    FilterValues = new Dictionary<string, object> { ["user_id"] = userId },
    Sort = new List<SortParamRequest>
    {
        new SortParamRequest { Field = "last_message_at", Direction = -1 }
    },
    Limit = 20
});
```

</codetabs-item>

</codetabs>

### Monitoring Query Performance

Use the [Stream Dashboard](https://beta.dashboard.getstream.io) to monitor and optimize your QueryChannels performance:

1. **Create Predefined Filters** for your frequently used query patterns
2. **View Performance Analysis** in the Dashboard once filters receive traffic
3. **Review Recommendations** for optimization opportunities
4. **Track Improvements** over time as you optimize your queries

<admonition type="info">

**Performance insights availability**: Performance scores and recommendations become available once a filter/sort combination receives significant traffic. Not all filters will show analysis immediately—the system needs sufficient usage data to provide meaningful insights.

</admonition>

## Predefined Filters

Predefined Filters are reusable, templated filter configurations that you create and manage in the [Stream Dashboard](https://beta.dashboard.getstream.io). They provide a recommended approach for production QueryChannels usage.

### Why Use Predefined Filters

- **Consistency**: Define filter logic once and reuse it across your application
- **Dashboard Management**: Create, update, and monitor filters through the Dashboard
- **Performance Insights**: View performance analysis for your filters directly in the Dashboard once they receive significant traffic
- **Dynamic Values**: Use placeholders for values that change at query time (like user IDs)

### Creating Predefined Filters

Create and manage Predefined Filters in the [Stream Dashboard](https://beta.dashboard.getstream.io). Navigate to your app's settings to define filter templates with placeholders for dynamic values.

### Using Predefined Filters

Reference a predefined filter by name and provide values for any placeholders:

<codetabs>

<codetabs-item value="go" label="Go">

```go
resp, err := client.Chat().QueryChannels(ctx, &getstream.QueryChannelsRequest{
	PredefinedFilter: getstream.PtrTo("user_messaging_channels"),
	FilterValues: map[string]any{
		"user_id": "user123",
	},
	Limit: getstream.PtrTo(20),
})
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
channels = client.chat.query_channels(
    predefined_filter="user_messaging_channels",
    filter_values={"user_id": "user123"},
    limit=20,
)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

channels = client.chat.query_channels(
  Models::QueryChannelsRequest.new(
    predefined_filter: 'user_messaging_channels',
    filter_values: { 'user_id' => 'user123' },
    limit: 20
  )
)
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\ChatClient;
use GetStream\GeneratedModels as Models;

$response = $client->queryChannels(new Models\QueryChannelsRequest(
    predefinedFilter: "user_messaging_channels",
    filterValues: (object)["user_id" => "user123"],
    limit: 20,
));
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Backend SDK
chat.queryChannels(QueryChannelsRequest.builder()
    .predefinedFilter("user_messaging_channels")
    .filterValues(Map.of("user_id", "user123"))
    .limit(20)
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
var resp = await chat.QueryChannelsAsync(new QueryChannelsRequest
{
    PredefinedFilter = "user_messaging_channels",
    FilterValues = new Dictionary<string, object> { ["user_id"] = "user123" },
    Limit = 20
});
```

</codetabs-item>

</codetabs>

### Placeholder Syntax

Placeholders use double curly braces: `{{placeholder_name}}`

When creating a predefined filter in the Dashboard, you can define templates like:

```json
{
  "type": "{{channel_type}}",
  "members": {
    "$in": "{{users}}"
  }
}
```

At query time, provide the actual values via `filter_values`:

```json
{
  "predefined_filter": "user_messaging_channels",
  "filter_values": {
    "channel_type": "messaging",
    "users": ["user123", "user456"]
  }
}
```

You can also use placeholders in sort field names. Provide these values via `sort_values`:

```json
{
  "predefined_filter": "team_channels",
  "filter_values": {
    "team_id": "engineering"
  },
  "sort_values": {
    "sort_field": "last_message_at"
  }
}
```

### Performance Analysis

The Dashboard displays performance analysis for your Predefined Filters. Performance scores and recommendations become available once a filter receives significant traffic or exhibits notable latency. Not all filters will show analysis immediately—the system needs sufficient usage data to provide meaningful insights.

## Performance Considerations

QueryChannels performance depends on your filter complexity and the volume of data. Understanding which fields perform well helps you build efficient queries.

### Well-Optimized Fields

These fields are indexed and perform efficiently at scale:

- `cid` (full channel ID)
- `type`
- `members`
- `created_at`
- `last_message_at`
- `last_updated`

### Fields to Use with Caution

These fields may have performance implications at scale:

- `member_count`: Can be slow for large datasets
- `frozen`: Limited index support
- **Complex nested queries**: Multiple `$and`/`$or` combinations

### Query Complexity

Simple, selective filters perform better than complex queries:

<codetabs>

<codetabs-item value="go" label="Go">

```go
// RECOMMENDED: Simple, selective filter with indexed fields
filter := map[string]any{
	"type": "messaging",
	"members": map[string]any{
		"$in": []string{userID},
	},
	"last_message_at": map[string]any{
		"$gte": thirtyDaysAgo,
	},
}
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# RECOMMENDED: Simple, selective filter with indexed fields
filter = {
    "type": "messaging",
    "members": {"$in": [user_id]},
    "last_message_at": {"$gte": thirty_days_ago},
}
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'

# RECOMMENDED: Simple, selective filter with indexed fields
filter = {
  'type' => 'messaging',
  'members' => { '$in' => [user_id] },
  'last_message_at' => { '$gte' => thirty_days_ago }
}
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
// RECOMMENDED: Simple, selective filter with indexed fields
$filter = (object)[
    "type" => "messaging",
    "members" => (object)['$in' => [$userId]],
    "last_message_at" => (object)['$gte' => $thirtyDaysAgo],
];
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// RECOMMENDED: Simple, selective filter with indexed fields
Map<String, Object> filter = Map.of(
    "type", "messaging",
    "members", Map.of("$in", List.of(userId)),
    "last_message_at", Map.of("$gte", thirtyDaysAgo));
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// RECOMMENDED: Simple, selective filter with indexed fields
var filter = new Dictionary<string, object>
{
    ["type"] = "messaging",
    ["members"] = new Dictionary<string, object> { ["$in"] = new[] { userId } },
    ["last_message_at"] = new Dictionary<string, object> { ["$gte"] = thirtyDaysAgo }
};
```

</codetabs-item>

</codetabs>

### Sort Performance

The most efficient sort fields are:

- `last_message_at`
- `last_updated`
- `created_at`

### Pagination Best Practices

For consistent and efficient pagination:

- **Use reasonable limits**: The default limit is 10 and max is 30. Larger page sizes increase response time and payload size.
- **Include a members filter**: Always include `members: { $in: [userID] }` in your filter for consistent pagination. Without this, channel list changes during pagination can cause channels to be skipped or duplicated.
- **Respect the offset maximum**: The maximum offset is 1000. For datasets larger than this, use time-based filtering (e.g., `last_message_at` or `created_at`) to paginate through older data.

<codetabs>

<codetabs-item value="go" label="Go">

```go
// Efficient pagination with members filter
resp, err := client.Chat().QueryChannels(ctx, &getstream.QueryChannelsRequest{
	FilterConditions: map[string]any{
		"type": "messaging",
		"members": map[string]any{
			"$in": []string{userID},
		},
	},
	Sort:  []getstream.SortParamRequest{{Field: getstream.PtrTo("last_message_at"), Direction: getstream.PtrTo(-1)}},
	Limit: getstream.PtrTo(20),
})
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import SortParamRequest

# Efficient pagination with members filter
channels = client.chat.query_channels(
    filter_conditions={"type": "messaging", "members": {"$in": [user_id]}},
    sort=[SortParamRequest(field="last_message_at", direction=-1)],
    limit=20,
)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Efficient pagination with members filter
channels = client.chat.query_channels(
  Models::QueryChannelsRequest.new(
    filter_conditions: { 'type' => 'messaging', 'members' => { '$in' => [user_id] } },
    sort: [Models::SortParamRequest.new(field: 'last_message_at', direction: -1)],
    limit: 20
  )
)
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\ChatClient;
use GetStream\GeneratedModels as Models;

// Efficient pagination with members filter
$response = $client->queryChannels(new Models\QueryChannelsRequest(
    filterConditions: (object)[
        "type" => "messaging",
        "members" => (object)['$in' => [$userId]],
    ],
    sort: [new Models\SortParamRequest(field: "last_message_at", direction: -1)],
    limit: 20,
));
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Backend SDK: Efficient pagination with members filter
chat.queryChannels(QueryChannelsRequest.builder()
    .filterConditions(Map.of(
        "type", "messaging",
        "members", Map.of("$in", List.of(userId))))
    .sort(List.of(SortParamRequest.builder().field("last_message_at").direction(-1).build()))
    .limit(20)
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// Efficient pagination with members filter
var resp = await chat.QueryChannelsAsync(new QueryChannelsRequest
{
    FilterConditions = new Dictionary<string, object>
    {
        ["type"] = "messaging",
        ["members"] = new Dictionary<string, object> { ["$in"] = new[] { userId } }
    },
    Sort = new List<SortParamRequest>
    {
        new SortParamRequest { Field = "last_message_at", Direction = -1 }
    },
    Limit = 20
});
```

</codetabs-item>

</codetabs>

### Recommendations

1. **Use Predefined Filters** for frequently used query patterns in production
2. **Filter by indexed fields** (`cid`, `type`, `members`, `last_message_at`, `created_at`)
3. **Add time-based filters** to limit the scan scope (e.g., `last_message_at` within last 30 days)
4. **Avoid deep nesting** of `$and`/`$or` operators
5. **Monitor performance** through the Dashboard when using Predefined Filters


---

This page was last updated at 2026-03-16T10:44:20.028Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/ruby/query_channels/](https://getstream.io/chat/docs/ruby/query_channels/).