# 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.

<codetabs>

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

```js
// 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 },
);
```

</codetabs-item>

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

```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
  }
}
```

</codetabs-item>

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

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

</codetabs-item>

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

```java
var searchResult = Message.search()
  .filterCondition("text", condition)
  .query("supercalifragilisticexpialidocious")
  .limit(10)
  .request();
```

</codetabs-item>

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

```python
channel_filters = {"cid": "messaging:my-channel"}
message_filters = {"text": {"$autocomplete": "supercali"}}

page1 = client.search(
  channel_filters,
  message_filters,
  sort=[{"relevance": -1}, {"updated_at": 1}],
  limit=10
)
```

</codetabs-item>

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

```ruby
channel_filters = { cid: 'messaging:my-channel' }
message_filters = { text: { '$autocomplete': 'supercali' }}

page1 = client.search(
  channel_filters,
  message_filters,
  sort: [{ relevance: -1 }, { updated_at: 1 }],
  limit: 10
)
```

</codetabs-item>

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

```php
$response = $serverClient->search(
  $filters,
  'supercalifragilisticexpialidocious',
  ['limit' => 10]
);
```

</codetabs-item>

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

```go
resp, err := client.Search(ctx, SearchRequest{
  Query: "supercalifragilisticexpialidocious",
  Filters: map[string]interface{}{
    "members": map[string][]string{
      "$in": {"john"},
    },
  },
  Limit: 10,
})
```

</codetabs-item>

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

```csharp
var response = await messageClient.SearchAsync(SearchOptions.Default
  .WithMessageFilterConditions(new Dictionary<string, object>
  {
    {
      "members", new Dictionary<string, object>
      {
        { "$in", new[] {"john"} },
      }
    },
  })
  .WithQuery("supercalifragilisticexpialidocious")
  .WithLimit(10));
```

</codetabs-item>

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

```csharp
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);
}
```

</codetabs-item>

</codetabs>

### Query Parameters

| Name                      | Type         | Description                                                                                                             | Default                    | Optional |
| ------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- | -------------------------- | -------- |
| filter_conditions         | object       | Channel filters. Maximum 500 channels are searched. See [Querying Channels](/chat/docs/<framework>/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.

<codetabs>

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

```js
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,
});
```

</codetabs-item>

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

```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 { /* ... */ }
```

</codetabs-item>

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

```python
channel_filters = {"cid": "messaging:my-channel"}
message_filters = {"text": {"$autocomplete": "supercali"}}

# First page
page1 = client.search(
  channel_filters,
  message_filters,
  sort=[{"relevance": -1}, {"updated_at": 1}, {"my_custom_field": -1}],
  limit=10
)

# Next page
page2 = client.search(channel_filters, message_filters, next=page1["next"], limit=10)

# Previous page
page1_again = client.search(channel_filters, message_filters, next=page2["previous"], limit=10)
```

</codetabs-item>

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

```ruby
channel_filters = { cid: 'messaging:my-channel' }
message_filters = { text: { '$autocomplete': 'supercali' }}

# First page
page1 = client.search(
  channel_filters,
  message_filters,
  sort: [{ relevance: -1 }, { updated_at: 1 }, { my_custom_field: -1 }],
  limit: 10
)

# Next page
page2 = client.search(channel_filters, message_filters, next: page1.next, limit: 10)

# Previous page
page1_again = client.search(channel_filters, message_filters, next: page2.previous, limit: 10)
```

</codetabs-item>

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

```java
var searchResult = Message.search()
  .filterCondition("text", condition)
  .query("supercalifragilisticexpialidocious")
  .limit(2)
  .request();

// Next page
var nextResult = Message.search()
  .filterCondition("text", condition)
  .query("supercalifragilisticexpialidocious")
  .sort(Sort.builder().field("relevance").direction(Sort.Direction.ASC).build())
  .sort(Sort.builder().field("updated_at").direction(Sort.Direction.DESC).build())
  .limit(2)
  .next(searchResult.getNext())
  .request();
```

</codetabs-item>

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

```php
$response = $serverClient->search(
  $filters,
  'supercalifragilisticexpialidocious',
  ['limit' => 10]
);

// Next page
$response = $serverClient->search(
  $filters,
  'supercalifragilisticexpialidocious',
  ['limit' => 10, 'next' => $response['next']]
);
```

</codetabs-item>

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

```go
resp, err := client.Search(ctx, SearchRequest{
  Query: "supercalifragilisticexpialidocious",
  Filters: map[string]interface{}{
    "members": map[string][]string{
      "$in": {"john"},
    },
  },
  Limit: 10,
})

// Next page
client.SearchWithFullResponse(ctx, SearchRequest{
  Query: "supercalifragilisticexpialidocious",
  Filters: map[string]interface{}{
    "members": map[string][]string{
      "$in": {"john"},
    },
  },
  Next: resp.Next,
  Limit: 10,
})
```

</codetabs-item>

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

```csharp
var response = await messageClient.SearchAsync(SearchOptions.Default
  .WithMessageFilterConditions(new Dictionary<string, object>
  {
    {
      "members", new Dictionary<string, object>
      {
        { "$in", new[] {"john"} },
      }
    },
  })
  .WithQuery("supercalifragilisticexpialidocious")
  .WithLimit(10));

// Next page
response = await messageClient.SearchAsync(SearchOptions.Default
  .WithMessageFilterConditions(new Dictionary<string, object>
  {
    {
      "members", new Dictionary<string, object>
      {
        { "$in", new[] {"john"} },
      }
    },
  })
  .WithQuery("supercalifragilisticexpialidocious")
  .WithLimit(10)
  .WithNext(response.Next));
```

</codetabs-item>

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

```csharp
// 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
});
```

</codetabs-item>

</codetabs>


---

This page was last updated at 2026-03-05T19:05:30.728Z.

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