# Threads & Replies

Threads allow users to reply to specific messages without cluttering the main channel conversation. A thread is created when a message is sent with a `parent_id` referencing another message.

## Starting a Thread

Send a message with a `parent_id` to start a thread or add a reply to an existing thread.

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

client.chat.send_message('messaging', channel_id, Models::SendMessageRequest.new(
  message: Models::MessageRequest.new(
    text: 'This is a reply in a thread',
    parent_id: parent_message_id,
    user_id: user_id
  )
))
```

### Thread Parameters

| Name            | Type    | Description                                                        | Default | Optional |
| --------------- | ------- | ------------------------------------------------------------------ | ------- | -------- |
| parent_id       | string  | ID of the parent message to reply to                               |         |          |
| show_in_channel | boolean | If true, the reply appears both in the thread and the main channel | false   | ✓        |

>
> **Info:** Messages in threads support the same features as regular messages: reactions, attachments, and mentions.
>

## Paginating Thread Replies

When querying a channel, thread replies are not included by default. The parent message includes a `reply_count` field. Use `getReplies` to fetch thread messages.

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

# Get the first 20 replies
client.chat.get_replies(parent_message_id, 20)

# Get older replies
client.chat.get_replies(parent_message_id, 20, nil, nil, nil, '42')
```

## Inline Replies

Reply to a message inline without creating a thread. The referenced message appears within the new message. Use `quoted_message_id` instead of `parent_id`.

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

client.chat.send_message('messaging', channel_id, Models::SendMessageRequest.new(
  message: Models::MessageRequest.new(
    text: 'I agree with this point',
    quoted_message_id: original_message_id,
    user_id: user_id
  )
))
```

When querying messages, the `quoted_message` field is automatically populated:

```json
{
  "id": "new-message-id",
  "text": "I agree with this point",
  "quoted_message_id": "original-message-id",
  "quoted_message": {
    "id": "original-message-id",
    "text": "The original message text"
  }
}
```

>
> **Warning:** Inline replies are only available one level deep. If Message A replies to Message B, and Message B replies to Message C, you cannot access Message C through Message A. Fetch Message B directly to access its referenced message.
>

## Thread List

Query all threads that the current user participates in. This is useful for building thread list views similar to Slack or Discord.

### Querying Threads

Threads are returned with unread replies first, sorted by the latest reply timestamp in descending order.

```js label="JavaScript"
const { threads } = await client.queryThreadsAndHydrate();

for (const thread of threads) {
  const state = thread.state.getLatestValue();
  console.log(state.parentMessage.text);
  console.log(thread.messagePaginator.items); // the latest replies
  console.log(state.participants);
  console.log(state.read);
}
```

**Example Response**

```json
{
  "threads": [
    {
      "channel_cid": "messaging:general",
      "channel": {
        "id": "general",
        "type": "messaging",
        "name": "General"
      },
      "parent_message_id": "parent-123",
      "parent_message": {
        "id": "parent-123",
        "text": "Original message",
        "type": "regular"
      },
      "created_by_user_id": "user-1",
      "reply_count": 5,
      "participant_count": 3,
      "thread_participants": [
        {
          "user_id": "user-1",
          "user": { "id": "user-1", "name": "Alice" }
        },
        {
          "user_id": "user-2",
          "user": { "id": "user-2", "name": "Bob" }
        }
      ],
      "last_message_at": "2024-12-11T15:30:00Z",
      "latest_replies": [
        {
          "id": "reply-1",
          "text": "Latest reply",
          "type": "reply"
        }
      ],
      "read": [
        {
          "user": { "id": "user-1" },
          "last_read": "2024-12-11T15:00:00Z",
          "unread_messages": 2
        }
      ]
    }
  ]
}
```

### Query Options

| Name              | Type    | Description                                       | Default | Optional |
| ----------------- | ------- | ------------------------------------------------- | ------- | -------- |
| reply_limit       | number  | Number of latest replies to fetch per thread      | 2       | ✓        |
| participant_limit | number  | Number of thread participants to fetch per thread | 100     | ✓        |
| limit             | number  | Maximum number of threads to return               | 10      | ✓        |
| watch             | boolean | If true, watch channels for the returned threads  | true    | ✓        |
| member_limit      | number  | Number of members to fetch per thread channel     | 100     | ✓        |

### Filtering and Sorting

Filter and sort threads using MongoDB-style [query operators](https://getstream.io/docs/platform/query-syntax-operators/).

#### Supported Filter Fields

| Field                | Type                      | Operators                           | Description               |
| -------------------- | ------------------------- | ----------------------------------- | ------------------------- |
| `channel_cid`        | string or list of strings | `$eq`, `$in`                        | Channel CID               |
| `channel.disabled`   | boolean                   | `$eq`                               | Channel disabled status   |
| `channel.team`       | string or list of strings | `$eq`, `$in`                        | Channel team              |
| `parent_message_id`  | string or list of strings | `$eq`, `$in`                        | Parent message ID         |
| `created_by_user_id` | string or list of strings | `$eq`, `$in`                        | Thread creator's user ID  |
| `created_at`         | string (RFC3339)          | `$eq`, `$gt`, `$lt`, `$gte`, `$lte` | Thread creation timestamp |
| `updated_at`         | string (RFC3339)          | `$eq`, `$gt`, `$lt`, `$gte`, `$lte` | Thread update timestamp   |
| `last_message_at`    | string (RFC3339)          | `$eq`, `$gt`, `$lt`, `$gte`, `$lte` | Last message timestamp    |

#### Supported Sort Fields

- `active_participant_count`
- `created_at`
- `last_message_at`
- `parent_message_id`
- `participant_count`
- `reply_count`
- `updated_at`

Use `1` for ascending order and `-1` for descending order.

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

filter = {
  'created_by_user_id' => { '$eq' => 'user-1' },
  'updated_at' => { '$gte' => '2024-01-01T00:00:00Z' }
}
sort = [Models::SortParamRequest.new(field: 'created_at', direction: -1)]

response = client.chat.query_threads(Models::QueryThreadsRequest.new(
  filter: filter,
  sort: sort,
  limit: 10,
  user_id: user_id
))

# Get next page
if response.next
  next_page = client.chat.query_threads(Models::QueryThreadsRequest.new(
    filter: filter,
    sort: sort,
    limit: 10,
    user_id: user_id,
    next: response.next
  ))
end
```

### Getting a Thread by ID

Retrieve a specific thread using the parent message ID.

```js label="JavaScript"
const thread = await client.getThreadAndHydrate(
  { message_id: parentMessageId },
  {
    watch: true,
    reply_limit: 10,
    participant_limit: 25,
  },
);
```

### Updating Thread Title and Custom Data

Assign a title and custom data to a thread.

```js label="JavaScript"
// Set properties
const { thread } = await client.updateThreadPartial(
  { message_id: threadId },
  {
    set: {
      title: "Project Discussion",
      priority: "high",
    },
  },
);

// Remove properties
await client.updateThreadPartial(
  { message_id: threadId },
  { unset: ["priority"] },
);
```

## Thread Unread Counts

### Total Unread Threads

The total number of unread threads is available after connecting.

```js label="JavaScript"
const response = await client.connectUser({ id: "user-id" }, token);
console.log(response?.me?.unread_threads);

// Or access via thread manager
client.threads.registerSubscriptions();
const { unreadThreadCount } = client.threads.state.getLatestValue();
```

### Marking Threads as Read or Unread

```js label="JavaScript"
// Mark thread as read
await channel.markRead({ thread_id: parentMessageId });

// Mark thread as unread
await channel.markUnread({ thread_id: parentMessageId });
```

### Unread Count Per Thread

```js label="JavaScript"
const response = await client.unreadCounts();

console.log(response.total_unread_threads_count);

for (const thread of response.threads) {
  console.log(thread.parent_message_id);
  console.log(thread.unread_count);
  console.log(thread.last_read);
}
```

## Thread Manager

The `ThreadManager` class provides built-in pagination and state management for threads.

```js label="JavaScript"
// Access the client's thread manager
const threadManager = client.threads;

// Subscribe to state updates
const unsubscribe = threadManager.state.subscribe((state) => {
  console.log(state.unreadThreadCount);
});

// Subscribe to the thread list
const unsubscribeList = threadManager.paginator.state.subscribe((state) => {
  console.log(state.items);
});

// Load threads
await threadManager.reload();

// Load more threads
await threadManager.paginator.toTail();

// Access current state
const { items: threads = [] } = threadManager.paginator.state.getLatestValue();
```

### Event Handling

Register subscriptions to receive real-time updates for threads.

```js label="JavaScript"
const { threads } = await client.queryThreadsAndHydrate({
  watch: true,
  limit: 10,
});
const [thread] = threads;

// Threads receive events through the client's thread manager
client.threads.registerSubscriptions();

// Open the thread: the thread manager keeps it up to date from now on
const release = thread.activate();

const unsubscribe = thread.messagePaginator.state.subscribe((state) => {
  console.log(state.items);
});
```

>
> **Info:** The `watch` parameter is required when querying threads to receive real-time updates.
>

For `ThreadManager`, call `registerSubscriptions` once to automatically manage subscriptions for all loaded threads:

```js label="JavaScript"
const threadManager = client.threads;
threadManager.registerSubscriptions();

await threadManager.reload();

// All loaded threads now receive their events
const { items: threads = [] } = threadManager.paginator.state.getLatestValue();
```

---

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