# Message Search

Search messages across channels using full-text search or specific field filters. Enable or disable search indexing per channel type in the Stream Dashboard.

## Searching Messages

Search requires a channel filter and either a text query or message filter conditions.

<Tabs>

```js label="JavaScript"
// Search for messages containing text
const results = await client.search(
  { members: { $in: ["john"] } },
  "supercalifragilisticexpialidocious",
  { limit: 10 },
);

// Search with message filters
const filtered = await client.search(
  { members: { $in: ["john"] } },
  { text: { $autocomplete: "super" }, attachments: { $exists: true } },
  { limit: 10 },
);
```

```kotlin label="Kotlin"
val channelFilter = Filters.eq("cid", "messaging:my-channel")
val messageFilter = Filters.autocomplete("text", "supercali")

client.searchMessages(
  channelFilter = channelFilter,
  messageFilter = messageFilter,
  limit = 10,
).enqueue { result ->
  if (result is Result.Success) {
    val messages: List<Message> = result.value.messages
  } else {
    // Handle Result.Failure
  }
}
```

```swift label="Swift"
let controller = client.messageSearchController()
controller.loadNextMessages(limit: 10) { error in
  if let error = error {
    // Handle error
  } else {
    let messages = controller.messages
  }
}
```

```java label="Java"
var searchResult = chat.search(SearchRequest.builder()
    .Payload(SearchPayload.builder()
        .filterConditions(Map.of("cid", "messaging:my-channel"))
        .messageFilterConditions(Map.of("text", Map.of("$autocomplete", "supercali")))
        .limit(10)
        .build())
    .build()).execute().getData();
```

```python label="Python"
from getstream.models import SearchPayload, SortParamRequest

page1 = client.chat.search(
    payload=SearchPayload(
        filter_conditions={"cid": "messaging:my-channel"},
        message_filter_conditions={"text": {"$autocomplete": "supercali"}},
        sort=[
            SortParamRequest(field="relevance", direction=-1),
            SortParamRequest(field="updated_at", direction=1),
        ],
        limit=10,
    )
)
```

```ruby label="Ruby"
require 'getstream_ruby'
Models = GetStream::Generated::Models

page1 = client.chat.search(Models::SearchPayload.new(
  filter_conditions: { 'cid' => 'messaging:my-channel' },
  message_filter_conditions: { 'text' => { '$autocomplete' => 'supercali' } },
  sort: [
    Models::SortParamRequest.new(field: 'relevance', direction: -1),
    Models::SortParamRequest.new(field: 'updated_at', direction: 1)
  ],
  limit: 10
))
```

```php label="PHP"
$response = $client->search(new Models\SearchPayload(
    filterConditions: $filters,
    query: "supercalifragilisticexpialidocious",
    limit: 10,
));
```

```go label="Go"
resp, err := client.Chat().Search(ctx, &getstream.SearchRequest{
  Payload: &getstream.SearchPayload{
    Query: getstream.PtrTo("supercalifragilisticexpialidocious"),
    FilterConditions: map[string]any{
      "members": map[string]any{
        "$in": []string{"john"},
      },
    },
    Limit: getstream.PtrTo(10),
  },
})
```

```csharp label="C#"
using System.Text.Json;

var resp = await chat.SearchAsync(new
{
    payload = JsonSerializer.Serialize(new
    {
        filter_conditions = new Dictionary<string, object>
        {
            ["members"] = new Dictionary<string, object> { ["$in"] = new[] { "john" } }
        },
        query = "supercalifragilisticexpialidocious",
        limit = 10
    })
});
```

```csharp label="Unity"
var results = await Client.LowLevelClient.MessageApi.SearchMessagesAsync(new SearchRequest
{
  FilterConditions = new Dictionary<string, object>
  {
    {
      "members", new Dictionary<string, object>
      {
        { "$in", new[] { "John" } }
      }
    }
  },
  Query = "supercalifragilisticexpialidocious",
  Limit = 30
});

foreach (var searchResult in results.Results)
{
  Debug.Log(searchResult.Message.Id);
  Debug.Log(searchResult.Message.Text);
  Debug.Log(searchResult.Message.User);
  Debug.Log(searchResult.Message.Channel);
}
```

</Tabs>

### Query Parameters

| Name                      | Type         | Description                                                                                                             | Default                      | Optional |
| ------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -------- |
| filter_conditions         | object       | Channel filters. Maximum 500 channels are searched. See [Querying Channels](/chat/docs/node/query_channels/).     | -                            |          |
| message_filter_conditions | object       | Message filters. See Message Filter Conditions below. Either this or `query` is required.                               | -                            | ✓        |
| query                     | string       | Full-text search string. Equivalent to `{text: {$q: <query>}}`. Either this or `message_filter_conditions` is required. | -                            | ✓        |
| limit                     | integer      | Number of messages to return.                                                                                           | 100                          | ✓        |
| offset                    | integer      | Pagination offset. Cannot be used with `sort` or `next`.                                                                | 0                            | ✓        |
| sort                      | object/array | Sort order. Use field names with `1` (ascending) or `-1` (descending).                                                  | `[{relevance: -1}, {id: 1}]` | ✓        |
| next                      | string       | Pagination cursor. See Pagination below.                                                                                | -                            | ✓        |

### Message Filter Conditions

| Field              | Description                                            | Operators                                         |
| ------------------ | ------------------------------------------------------ | ------------------------------------------------- |
| id                 | Message ID                                             | $eq, $gt, $gte, $lt, $lte, $in                    |
| text               | Message text                                           | $q, $autocomplete, $eq, $gt, $gte, $lt, $lte, $in |
| type               | Message type. System and deleted messages are excluded | $eq, $gt, $gte, $lt, $lte, $in                    |
| parent_id          | Parent message ID (for replies)                        | $eq, $gt, $gte, $lt, $lte, $in                    |
| reply_count        | Number of replies                                      | $eq, $gt, $gte, $lt, $lte, $in                    |
| attachments        | Whether message has attachments                        | $exists, $eq, $gt, $gte, $lt, $lte, $in           |
| attachments.type   | Attachment type                                        | $eq, $in                                          |
| mentioned_users.id | Mentioned user ID                                      | $contains                                         |
| user.id            | Sender user ID                                         | $eq, $gt, $gte, $lt, $lte, $in                    |
| created_at         | Creation timestamp                                     | $eq, $gt, $gte, $lt, $lte, $in                    |
| updated_at         | Update timestamp                                       | $eq, $gt, $gte, $lt, $lte, $in                    |
| pinned             | Whether message is pinned                              | $eq                                               |
| custom field       | Any custom field on the message                        | $eq, $gt, $gte, $lt, $lte, $in                    |

## Sorting

Results are sorted by relevance by default, with message ID as a tiebreaker. If your query does not use `$q` or `$autocomplete`, all results are equally relevant.

Sort by any filterable field, including custom fields. Numeric custom fields are sorted numerically; string fields are sorted lexicographically.

## Pagination

Two pagination methods are available:

**Offset pagination** allows access to up to 1,000 results. Results are sorted by relevance and message ID. You cannot use custom sorting with offset pagination.

**Cursor pagination** (using `next`/`previous`) allows access to all matching results with custom sorting. The response includes `next` and `previous` cursors to navigate between pages.

<Tabs>

```js label="JavaScript"
const channelFilters = { cid: "messaging:my-channel" };
const messageFilters = { text: { $autocomplete: "supercali" } };

// First page with custom sorting
const page1 = await client.search(channelFilters, messageFilters, {
  sort: [{ relevance: -1 }, { updated_at: 1 }, { my_custom_field: -1 }],
  limit: 10,
});

// Next page using cursor
const page2 = await client.search(channelFilters, messageFilters, {
  limit: 10,
  next: page1.next,
});

// Previous page
const page1Again = await client.search(channelFilters, messageFilters, {
  limit: 10,
  next: page2.previous,
});
```

```kotlin label="Kotlin"
val channelFilter = Filters.eq("cid", "messaging:my-channel")
val messageFilter = Filters.autocomplete("text", "supercali")
val sort = QuerySortByField.descByName<Message>("relevance")
  .descByName("updatedAt")
  .descByName("my_custom_field")

var nextPage: String? = null

client.searchMessages(
  sort = sort,
  limit = 10,
  channelFilter = channelFilter,
  messageFilter = messageFilter,
).enqueue { result ->
  if (result is Result.Success) {
    nextPage = result.value.next
    val messages: List<Message> = result.value.messages
  }
}

// Next page
var previousPage: String? = null
client.searchMessages(
  limit = 10,
  channelFilter = channelFilter,
  messageFilter = messageFilter,
  next = nextPage,
).enqueue { result ->
  if (result is Result.Success) {
    previousPage = result.value.previous
    val messages: List<Message> = result.value.messages
  }
}

// Previous page
client.searchMessages(
  limit = 10,
  channelFilter = channelFilter,
  messageFilter = messageFilter,
  next = previousPage,
).enqueue { /* ... */ }
```

```python label="Python"
from getstream.models import SearchPayload, SortParamRequest

channel_filters = {"cid": "messaging:my-channel"}
message_filters = {"text": {"$autocomplete": "supercali"}}
sort = [
    SortParamRequest(field="relevance", direction=-1),
    SortParamRequest(field="updated_at", direction=1),
    SortParamRequest(field="my_custom_field", direction=-1),
]

# First page
page1 = client.chat.search(
    payload=SearchPayload(
        filter_conditions=channel_filters,
        message_filter_conditions=message_filters,
        sort=sort,
        limit=10,
    )
)

# Next page
page2 = client.chat.search(
    payload=SearchPayload(
        filter_conditions=channel_filters,
        message_filter_conditions=message_filters,
        limit=10,
        next=page1.data.next,
    )
)

# Previous page
page1_again = client.chat.search(
    payload=SearchPayload(
        filter_conditions=channel_filters,
        message_filter_conditions=message_filters,
        limit=10,
        next=page2.data.previous,
    )
)
```

```ruby label="Ruby"
require 'getstream_ruby'
Models = GetStream::Generated::Models

channel_filters = { 'cid' => 'messaging:my-channel' }
message_filters = { 'text' => { '$autocomplete' => 'supercali' } }
sort = [
  Models::SortParamRequest.new(field: 'relevance', direction: -1),
  Models::SortParamRequest.new(field: 'updated_at', direction: 1),
  Models::SortParamRequest.new(field: 'my_custom_field', direction: -1)
]

# First page
page1 = client.chat.search(Models::SearchPayload.new(
  filter_conditions: channel_filters,
  message_filter_conditions: message_filters,
  sort: sort,
  limit: 10
))

# Next page
page2 = client.chat.search(Models::SearchPayload.new(
  filter_conditions: channel_filters,
  message_filter_conditions: message_filters,
  limit: 10,
  next: page1.next
))

# Previous page
page1_again = client.chat.search(Models::SearchPayload.new(
  filter_conditions: channel_filters,
  message_filter_conditions: message_filters,
  limit: 10,
  next: page2.previous
))
```

```java label="Java"
var searchResult = chat.search(SearchRequest.builder()
    .Payload(SearchPayload.builder()
        .filterConditions(Map.of("cid", "messaging:my-channel"))
        .messageFilterConditions(Map.of("text", Map.of("$autocomplete", "supercali")))
        .sort(List.of(
            SortParamRequest.builder().field("relevance").direction(-1).build(),
            SortParamRequest.builder().field("updated_at").direction(1).build()))
        .limit(2)
        .build())
    .build()).execute().getData();

// Next page
var nextResult = chat.search(SearchRequest.builder()
    .Payload(SearchPayload.builder()
        .filterConditions(Map.of("cid", "messaging:my-channel"))
        .messageFilterConditions(Map.of("text", Map.of("$autocomplete", "supercali")))
        .sort(List.of(
            SortParamRequest.builder().field("relevance").direction(-1).build(),
            SortParamRequest.builder().field("updated_at").direction(1).build()))
        .limit(2)
        .next(searchResult.getNext())
        .build())
    .build()).execute().getData();
```

```php label="PHP"
// First page
$response = $client->search(new Models\SearchPayload(
    filterConditions: $filters,
    query: "supercalifragilisticexpialidocious",
    limit: 10,
));

// Next page
$response = $client->search(new Models\SearchPayload(
    filterConditions: $filters,
    query: "supercalifragilisticexpialidocious",
    limit: 10,
    next: $response->getData()->next,
));
```

```go label="Go"
// First page
resp, err := client.Chat().Search(ctx, &getstream.SearchRequest{
  Payload: &getstream.SearchPayload{
    Query: getstream.PtrTo("supercalifragilisticexpialidocious"),
    FilterConditions: map[string]any{
      "members": map[string]any{
        "$in": []string{"john"},
      },
    },
    Limit: getstream.PtrTo(10),
  },
})

// Next page
client.Chat().Search(ctx, &getstream.SearchRequest{
  Payload: &getstream.SearchPayload{
    Query: getstream.PtrTo("supercalifragilisticexpialidocious"),
    FilterConditions: map[string]any{
      "members": map[string]any{
        "$in": []string{"john"},
      },
    },
    Next:  resp.Data.Next,
    Limit: getstream.PtrTo(10),
  },
})
```

```csharp label="C#"
using System.Text.Json;

// First page
var resp = await chat.SearchAsync(new
{
    payload = JsonSerializer.Serialize(new
    {
        filter_conditions = new Dictionary<string, object>
        {
            ["members"] = new Dictionary<string, object> { ["$in"] = new[] { "john" } }
        },
        query = "supercalifragilisticexpialidocious",
        limit = 10
    })
});

// Next page
var nextResp = await chat.SearchAsync(new
{
    payload = JsonSerializer.Serialize(new
    {
        filter_conditions = new Dictionary<string, object>
        {
            ["members"] = new Dictionary<string, object> { ["$in"] = new[] { "john" } }
        },
        query = "supercalifragilisticexpialidocious",
        limit = 10,
        next = resp.Data.Next
    })
});
```

```csharp label="Unity"
// First page
var resultsPage1 = await Client.LowLevelClient.MessageApi.SearchMessagesAsync(new SearchRequest
{
  FilterConditions = new Dictionary<string, object>
  {
    {
      "members", new Dictionary<string, object>
      {
        { "$in", new[] { "John" } }
      }
    }
  },
  Query = "supercalifragilisticexpialidocious",
  Limit = 30
});

// Next page
var resultsPage2 = await Client.MessageApi.SearchMessagesAsync(new SearchRequest
{
  FilterConditions = new Dictionary<string, object>
  {
    {
      "members", new Dictionary<string, object>
      {
        { "$in", new[] { "John" } }
      }
    }
  },
  Next = resultsPage1.Next,
  Query = "supercalifragilisticexpialidocious",
  Limit = 30
});
```

</Tabs>


---

This page was last updated at 2026-05-13T13:39:00.955Z.

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