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.

// Search for messages containing text
var results = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest
{
  // Channel filter is required - here, channels the local user is a member of
  ChannelFilter = new IFieldFilterRule[]
  {
    ChannelFilter.Members.In("john"),
  },
  Query = "supercalifragilisticexpialidocious",
  Limit = 10,
});

foreach (var hit in results.Results)
{
  Debug.Log(hit.Message.Id); // Stateful IStreamMessage
  Debug.Log(hit.Message.Text);
  Debug.Log(hit.Message.User);
  Debug.Log(hit.Channel.Cid); // Stateful IStreamChannel (auto-watched by default)
}

// Search with message filters - mutually exclusive with Query
var filtered = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest
{
  ChannelFilter = new IFieldFilterRule[]
  {
    ChannelFilter.Members.In("john"),
  },
  MessageFilter = new IFieldFilterRule[]
  {
    MessageFilter.Text.Autocomplete("super"),
    MessageFilter.AttachmentType.In("image", "video"),
  },
  Limit = 10,
  // Set to false for one-off search bars where you don't want every result
  // channel to start receiving realtime updates.
  WatchResultChannels = true,
});

Query Parameters

NameTypeDescriptionDefaultOptional
filter_conditionsobjectChannel filters. Maximum 500 channels are searched. See Querying Channels.-
message_filter_conditionsobjectMessage filters. See Message Filter Conditions below. Either this or query is required.-
querystringFull-text search string. Equivalent to {text: {$q: <query>}}. Either this or message_filter_conditions is required.-
limitintegerNumber of messages to return.100
offsetintegerPagination offset. Cannot be used with sort or next.0
sortobject/arraySort order. Use field names with 1 (ascending) or -1 (descending).[{relevance: -1}, {id: 1}]
nextstringPagination cursor. See Pagination below.-

Message Filter Conditions

FieldDescriptionOperators
idMessage ID$eq, $gt, $gte, $lt, $lte, $in
textMessage text$q, $autocomplete, $eq, $gt, $gte, $lt, $lte, $in
typeMessage type. System and deleted messages are excluded$eq, $gt, $gte, $lt, $lte, $in
parent_idParent message ID (for replies)$eq, $gt, $gte, $lt, $lte, $in
reply_countNumber of replies$eq, $gt, $gte, $lt, $lte, $in
attachmentsWhether message has attachments$exists, $eq, $gt, $gte, $lt, $lte, $in
attachments.typeAttachment type$eq, $in
mentioned_users.idMentioned user ID$contains
user.idSender user ID$eq, $gt, $gte, $lt, $lte, $in
created_atCreation timestamp$eq, $gt, $gte, $lt, $lte, $in
updated_atUpdate timestamp$eq, $gt, $gte, $lt, $lte, $in
pinnedWhether message is pinned$eq
custom fieldAny 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.

var channelFilters = new IFieldFilterRule[]
{
  ChannelFilter.Cid.EqualsTo("messaging:my-channel"),
};
var messageFilters = new IFieldFilterRule[]
{
  MessageFilter.Text.Autocomplete("supercali"),
};

// First page with custom sorting
var page1 = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest
{
  ChannelFilter = channelFilters,
  MessageFilter = messageFilters,
  Sort = MessagesSort
    .OrderByDescending(MessageSortFieldName.Relevance)
    .ThenByAscending(MessageSortFieldName.UpdatedAt),
  Limit = 10,
});

// Next page using the cursor returned by the previous response
var page2 = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest
{
  ChannelFilter = channelFilters,
  MessageFilter = messageFilters,
  Limit = 10,
  Next = page1.Next,
});

// Previous page
var page1Again = await Client.SearchMessagesAsync(new StreamSearchMessagesRequest
{
  ChannelFilter = channelFilters,
  MessageFilter = messageFilters,
  Limit = 10,
  Next = page2.Previous,
});