# Search

<admonition type="info">

Stream recently upgraded its search capabilities to provide more options for sorting and pagination. Applications created after November 3rd, 2021 will automatically be using the latest version. Accounts created before November 3rd, 2021 need to [contact support](https://getstream.io/contact/) to have the latest version enabled.

</admonition>

Message search is built-in to the chat API. You can enable and/or disable the search indexing on a per channel type through the Stream Dashboard.

The command shown below selects the channels in which John is a member. Next, it searches the messages in those channels for the keyword “'supercalifragilisticexpialidocious'”.

## Query Parameters

| name                      | type                       | description                                                                                                                                                                                                                                                                  | default                    | optional |
| ------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -------- |
| filter_conditions         | object                     | Filter conditions for channels. We will only ever search a maximum of 500 channels at a time, so it is beneficial to make these filters as strict as possible. See the [Query Channels](/chat/docs/<framework>/query_channels/) section for information about these filters. | -                          |          |
| message_filter_conditions | object                     | Filter conditions for messages. See the next section for details. You must either specify query or message_filter_conditions.                                                                                                                                                | -                          | ✓        |
| query                     | string                     | A string to search for (which is a full text search). This is equivalent passing {text: {$q: <query>}} to message_filter_conditions. You must either specify query or message_filter_conditions.                                                                             | -                          | ✓        |
| limit                     | integer                    | The number of messages to return.                                                                                                                                                                                                                                            | 100                        | ✓        |
| offset                    | integer                    | The pagination offset. See the Pagination section for more information. You cannot use a non-zero offset with the sort or the next parameters.                                                                                                                               | 0                          | ✓        |
| sort                      | object or array of objects | The sorting used for the messages matching the filters. Sorting is based on field and direction, and multiple sorting options can be provided. Direction can be ascending (1) or descending (-1).                                                                            | [{relevance: -1}, {id: 1}] | ✓        |
| next                      | string                     | A key used to paginate. See the Pagination section for more information.                                                                                                                                                                                                     | -                          | ✓        |

## Message Filter Conditions

You can use following operators and fields in order to search messages:

| Field              | Description                                                                               | Allowed operators                                 |
| ------------------ | ----------------------------------------------------------------------------------------- | ------------------------------------------------- |
| id                 | message ID                                                                                | $eq, $gt, $gte, $lt, $lte, $in                    |
| text               | text of the message                                                                       | $q. $autocomplete, $eq, $gt, $gte, $lt, $lte, $in |
| type               | type of the message. Messages with type 'system' and 'deleted' are excluded from results. | $eq, $gt, $gte, $lt, $lte, $in                    |
| parent_id          | the parent message ID (if the message is a reply)                                         | $eq, $gt, $gte, $lt, $lte, $in                    |
| reply_count        | number of replies the message has                                                         | $eq, $gt, $gte, $lt, $lte, $in                    |
| attachments        | whether or not the message contains an attachment                                         | $exists, $eq, $gt, $gte, $lt, $lte, $in           |
| attachments.type   | the type of the attachment                                                                | $eq, $in                                          |
| mentioned_users.id | user ID that is included in the message's mentions                                        | $contains                                         |
| user.id            | user ID of the user that sent the message                                                 | $eq, $gt, $gte, $lt, $lte, $in                    |
| created_at         | created at time                                                                           | $eq, $gt, $gte, $lt, $lte, $in                    |
| updated_at         | updated at time                                                                           | $eq, $gt, $gte, $lt, $lte, $in                    |
| pinned             | whether the message has been pinned                                                       | $eq                                               |
| <custom field>     | any custom field that is attached to your message                                         | $eq, $gt, $gte, $lt, $lte, $in                    |

<admonition type="info">

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

</admonition>

## Sorting

Messages default to being sorted by relevance to your query, with the message ID as a tie-breaker for any equally relevant results. If your query does not use the $q or $autocomplete operators then all results are considered equally relevant.

You can sort by any of the filter-able fields, including custom fields. Custom fields that are numbers will be sorted numerically, while custom string fields will be sorted lexicographically. See the Pagination section for examples of sorting.

## Pagination

There are two ways to paginate through search results:

1. Version 1 - Using limit and offset

2. Version 2 - Using limit and next/previous values

Limit and offset will allow you to access up to 1000 results matching your query. You will not be able to sort using limit and offset. The results will instead be sorted by relevance and message ID. See the code at the top of the page for examples of limit and offset pagination.

Next pagination will allow you to access _all_ search results that match your query, and you will be able to sort using any filter-able fields and custom fields.

Pages of sort results will be returned with next and previous strings, which tell the API where to start searching from and what sort order to use. You can supply those values as a next parameter when making a query to get a new page of results.

<codetabs>

<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")
// first 10 results
// sorted by relevance in descending order, then updated_at in ascending order,
// and then my_custom_field in descending order
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
  } else {
    // Handle Result.Failure
  }
}

// next 10 results
// this query will use the same sort order as in your first query,
// as the sort order is embedded in the next value
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
  } else {
    // Handle Result.Failure
  }
}

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

</codetabs-item>

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

```js
const channelFilters = { cid: "messaging:my-channel" };
const messageFilters = { text: { $autocomplete: "supercali" } };
// first 10 results
// sorted by relevance in descending order, then updated_at in ascending order,
// and then my_custom_field in descending order
const page1 = await client.search(channelFilters, messageFilters, {
  sort: [{ relevance: -1 }, { updated_at: 1 }, { my_custom_field: -1 }],
  limit: 10,
});

// next 10 results
// this query will use the same sort order as in your first query,
// as the sort order is embedded in the next value
const page2 = await client.search(channelFilters, messageFilters, {
  limit: 10,
  next: page1.next,
});

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

</codetabs-item>

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

```ruby
# require 'stream-chat'

channelFilters = { cid: 'messaging:my-channel' }
messageFilters = { text: { $autocomplete: 'supercali' }}

# first 10 results
# sorted by relevance in descending order, then updated_at in ascending order,
# and then my_custom_field in descending order
page1 = client.search(channelFilters, messageFilters,
 sort: [{ relevance: -1 }, { updated_at: 1 }, { my_custom_field: -1 }],
 limit: 10)

# next 10 results
# this query will use the same sort order as in your first query,
# as the sort order is embedded in the next value
page2 = client.search(channelFilters, messageFilters, next: page1.next, limit: 10)

# the previous 10 results
page1Again = client.search(channelFilters, messageFilters, next: page2.previous, limit: 10)
```

</codetabs-item>

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

```python
channelFilters = { "cid": 'messaging:my-channel' }
messageFilters = { "text": { "$autocomplete": 'supercali' }}

# first 10 results
# sorted by relevance in descending order, then updated_at in ascending order,
# and then my_custom_field in descending order
page1 = client.search(channelFilters, messageFilters,
 sort: [{ "relevance": -1 }, { "updated_at": 1 }, { "my_custom_field": -1 }],
 limit: 10)

# next 10 results
# this query will use the same sort order as in your first query,
# as the sort order is embedded in the next value
page2 = client.search(channelFilters, messageFilters, next=page1["next"], limit=10)

# the previous 10 results
page1Again = client.search(channelFilters, messageFilters, 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)
  .next(searchResult.getNext())
  .request();

// And now paginate the with the "next" token:
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' => 2]

// And now paginate the with the "next" token:
$response = $serverClient->search(
	$filters,
	'supercalifragilisticexpialidocious',
	['limit' => 2, '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: 3,
})

// And now paginate the with the "next" token:
client.SearchWithFullResponse(ctx, SearchRequest{
	Query: "supercalifragilisticexpialidocious",
	Filters: map[string]interface{}{
		"members": map[string][]string{
			"$in": {"john"},
		},
	},
	Next: resp.Next,
	Limit: 3,
})
```

</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(3));

// And now paginate the with the "next" token:

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

</codetabs-item>

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

```csharp
// Get first page without setting the Next parameter
var resultsPage1 = await Client.LowLevelClient.MessageApi.SearchMessagesAsync(new SearchRequest
{
	//Filter is required for search
	FilterConditions = new Dictionary<string, object>
	{
		{
			//Get channels that local user is a member of
			"members", new Dictionary<string, object>
			{
				{ "$in", new[] { "John" } }
			}
		}
	},
	Query = "supercalifragilisticexpialidocious", // Search phrase
	Limit = 30 // Per page results
});

// First page results
foreach (var searchResult in resultsPage1.Results)
{
	Debug.Log(searchResult.Message.Id); // Message ID
	Debug.Log(searchResult.Message.Text); // Message text
	Debug.Log(searchResult.Message.User); // Message author info
	Debug.Log(searchResult.Message.Channel); // Channel info
}

// Get second page of results by setting: Next = resultsPage1.Next
var resultsPage2 = await Client.MessageApi.SearchMessagesAsync(new SearchRequest
{
	// Filter is required for search
	FilterConditions = new Dictionary<string, object>
	{
		{
			//Get channels that local user is a member of
			"members", new Dictionary<string, object>
			{
				{ "$in", new[] { "John" } }
			}
		}
	},

	Next = resultsPage1.Next, // Put Next key from the previous page request
	Query = "supercalifragilisticexpialidocious", // Search phrase
	Limit = 30 // Per page results
});

// Second page results
foreach (var searchResult in resultsPage1.Results)
{
	Debug.Log(searchResult.Message.Id); // Message ID
	Debug.Log(searchResult.Message.Text); // Message text
	Debug.Log(searchResult.Message.User); // Message author info
	Debug.Log(searchResult.Message.Channel); // Channel info
}
```

</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
    // Use messages
  }
}
```

</codetabs-item>

</codetabs>

---

This page was last updated at 2025-08-15T14:14:42.385Z.

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