Message threads

The message threads feature organizes conversations into threads, making it easier to follow and respond to specific topics.

Users need threads to:

  • Set topic-specific discussions - within group chats or 1:1 conversations, you can initiate or participate in separate threads dedicated to specific topics, ensuring focused and structured discussions.

  • Clarify and resolve issues - message threads let you address specific problems or questions within a conversation, allowing you to provide relevant input and ensure clarity and efficient problem-solving.

  • Reduce confusion - by allowing you to focus the conversation in a separate thread, you minimize cross-talk and make the conversation easier to follow.

In the Chat SDK, a thread is a separate channel created for a selected message. Such a thread channel gets the ID starting with PUBNUB_INTERNAL_THREAD followed by the channel ID and message ID (separated by underscores). The message for which you create a thread channel has the hasThread parameter set to true to distinguish it from other messages. All messages in a thread (thread messages) are ordered by timetokens containing info on when messages were published.

Each thread message (threadMessage) and thread channel (threadChannel) are separate entities in the Chat SDK that extend the message and channel entities. This means that they offer separate methods for handling threads but also let you use all methods under the message and channel entities (for example, for editing or deleting).

Interactive demo

Check how a sample implementation could look like in a React app showcasing how to reply to messages in threads and quote messages sent by others.

Want to implement something similar?

Read how to do that or go straight to the demo's source code.

Test it out

Click Reset App State Globally to clear all previously added quotes and replies (if there are any).

Reply to a message in a thread by typing in a message in the input field and pressing the arrow to send it.

Create thread

createThread() creates a thread (channel) for a selected message.

icon

Under the hood

Method signature

This method has the following signature:

message.createThread(): Promise<ThreadChannel>

Input

This method doesn't take any parameters.

Output

TypeDescription
Promise<ThreadChannel>Object returning the thread channel metadata for the message updated with the hasThread parameter (and a threadRootId action type underneath).

Errors

Whenever you try to create a thread for a message that is already in a thread (published on a channel with the ID starting with MESSAGE_THREAD_ID_PREFIX), you'll receive the Only one level of thread nesting is allowed error. If you try to create a thread for a message that already contains a thread, you'll get the Thread for this message already exists error.

Basic usage

Create a thread for the last message on the support channel.

// reference the channel with the message
const channel = await chat.getChannel("support")
// get the last message on this channel
const message = (await channel.getHistory({count: 1})).messages[0]
// create a thread for this message
const threadChannel = await message.createThread()

Send thread message

Reply to a message in a thread by calling the sendText() method from the previously created threadChannel object.

Method signature

Head over to the sendText() method section for details.

Basic usage

Send a message in a thread created for the last message on the support channel.

// reference the channel where you want to send a message
const channel = await chat.getChannel("support")
// get the last message on the channel to which you want to reply in a thread
const message = (await channel.getHistory({count: 1})).messages[0]
// get the thread channel
const threadChannel = message.getThread()
// send a message in the thread
threadChannel.sendText("Good job, guys!")

Get thread

Get the thread channel on which the thread message is published.

icon

Under the hood

Method signature

This method has the following signature:

message.getThread(): Promise<ThreadChannel>

Input

This method doesn't take any parameters.

Output

TypeDescription
Promise<ThreadChannel>Object returning the thread channel metadata.

Basic usage

Get the thread channel created from the message with the 16200000000000001 timetoken.

// reference the "incident-management" parent channel
const channel = await chat.getChannel("incident-management")
// return the message object
const message = await channel.getMessage("16200000000000001")
// get the thread channel
message.getThread()

Utility getter function

You can use the hasThread getter method to check if a given message starts a thread.

Method signature

This method has the following signature:

message.hasThread: boolean

Properties

PropertyTypeDescription
hasThreadbooleanInfo on whether the message already starts a thread or not.

Basic usage

Check if the message with the 16200000000000001 timetoken starts a thread.

// reference the "message" that you're interested in
const message = await channel.getHistory({
startTimetoken: "16200000000000000",
endTimetoken: "162000000000000001",
})

// check if the message starts a thread
message.hasThread

Get thread updates

You can receive updates when specific message threads and related message reactions are added, edited, or removed on other clients using the streamUpdatesOn() on the ThreadMessage object.

This method accepts a callback function as an argument. The Chat SDK invokes this callback whenever someone adds, edits or deletes a message, or adds or removes a message reaction to/from the specific message thread(s).

Underneath, this method subscribes the current user to a channel and adds a message reactions event listener to receive all messageAction events of type added or removed. These methods also return the unsubscribe function you can invoke to stop receiving messageAction events and unsubscribe from the channel.

Method signature

icon

Under the hood


This method takes the following parameters:

static ThreadMessage.streamUpdatesOn(
threadMessages: ThreadMessage[]
callback: (threadMessages: ThreadMessage[]) => unknown
): () => void

Input

ParameterTypeRequiredDefaultDescription
threadMessagesThreadMessage[]Yesn/aArray of ThreadMessage objects for which you want to get updates on changed message threads or related message reactions.
callbackn/aYesn/aCallback function passed to the method as a parameter. It defines the custom behavior to be executed when detecting changes in message threads or related message reactions.
 → threadMessagesThreadMessage[]Yesn/aReturned array of ThreadMessage objects with the updated message threads or related reactions.

Output

TypeDescription
() => voidFunction you can call to disconnect (unsubscribe) from the channel and stop receiving objects events.

Errors

Whenever a list of ThreadMessage objects is required as a parameter, and you try to get updates on message threads and message reactions without specifying the list, you will receive the Cannot stream message updates on an empty list error.

Basic usage

Get message threads and message reaction-related updates for the first page of messages published in a thread on the support channel.

const channel = await chat.getChannel("support")
const { threadMessages } = await channel.getHistory()
ThreadMessages.streamUpdatesOn(threadMessages, (threadMessages) => {
console.log("Updated messages: ", threadMessages)
})

Other examples

Stop listening to updates for the last ten messages published in a thread on the support channel.

const channel = await chat.getChannel("support")
const { threadMessages } = await channel.getHistory()
const stopUpdates = ThreadMessages.streamUpdatesOn(threadMessages, /* handle updates callback */)
// after some time...
stopUpdates()

Get historical thread message

getHistory() called on the threadChannel object fetches historical thread messages from that thread channel.

icon

Under the hood

Method signature

This method takes the following parameters:

threadChannel.getHistory({
startTimetoken?: string;
endTimetoken?: string;
count?: number;
}): Promise<{
messages: ThreadMessage[];
isMore: boolean;
}>

Input

ParameterTypeRequiredDefaultDescription
startTimetokenstringNon/aTimetoken delimiting the start of a time slice (exclusive) to pull thread messages from. For details, refer to the Fetch History section.
endTimetokenstringNon/aTimetoken delimiting the end of a time slice (inclusive) to pull thread messages from. For details, refer to the Fetch History section.
countnumberNo25Number of historical thread messages to return for the channel in a single call. Since each call returns all attached message reactions by default, the maximum number of returned thread messages is 25. For more details, refer to the description of the includeMessageActions parameter in the JavaScript SDK docs.

Output

ParameterTypeDescription
Promise<>objectReturned object containing two fields: messages and isMore.
 → messagesThreadMessage[]Array listing the requested number of historical thread Message objects.
 → isMorebooleanWhether there are any more historical thread messages to pull.

By default, each call returns all message reactions and metadata attached to the retrieved thread messages.

Basic usage

From the thread created for the last message in the support channel, fetch 10 historical thread messages that are older than the timetoken 15343325214676133.

// reference the "support" channel
const channel = await chat.getChannel("support")
// get the last message on the channel, which is the root message for the thread
const message = (await channel.getHistory({count: 1})).messages[0]
// get the thread channel
const threadChannel = message.getThread()
// fetch the required historical messages
threadMessages = await threadChannel.getHistory(
{
startTimetoken: "15343325214676133",
count: 10
}
)

Pin thread message to thread channel

pinMessage() called on the ThreadChannel object pins a selected thread message to the thread channel.

icon

Under the hood

Method signature

This method takes the following parameters:

threadChannel.pinMessage(
message: ThreadMessage
): Promise<ThreadChannel>

Input

ParameterTypeRequiredDefaultDescription
messageThreadMessageYesn/aThreadMessage object you want to pin to the selected thread channel.

Output

TypeDescription
Promise<ThreadChannel>Object returning the thread channel metadata updated with these custom fields:

pinnedMessageTimetoken to mark the timetoken when the message was pinned

pinnedMessageChannelID to mark the channel on which the message was pinned to the thread channel (unpinning was performed either directly on the parent channel or on a thread channel).

Basic usage

A thread was created for the last message in the support parent channel. Pin the last message from this thread to the thread channel.

// reference the "support" channel where the root message for the thread is published
const channel = await chat.getChannel("support")
// get the last message on the channel, which is the root message for the thread
const message = (await channel.getHistory({count: 1})).messages[0]
// get the thread channel
const threadChannel = message.getThread()
// fetch the last message in the thread that you want to pin
const lastMessage = (await threadChannel.getHistory({count: 1})).messages[0]
// pin the message to the thread channel
const pinnedMessage = await threadChannel.pinMessage(lastMessage)

Pin thread message to parent channel

You can pin a selected thread message to the parent channel with pinMessageToParentChannel() and pinToParentChannel(). They give the same output, and the only difference is that you call a given method either on the ThreadChannel or the ThreadMessage object. Depending on the object, these methods take different input parameters - you have to specify the thread message you want to pin or not because it's already known.

icon

Under the hood

Method signature

These methods take the following parameters:

  • pinMessageToParentChannel()

    threadChannel.pinMessageToParentChannel(
    message: ThreadMessage
    ): Promise<Channel>
  • pinToParentChannel()

    threadMessage.pinToParentChannel(): Promise<Channel>

Input

ParameterTypeRequired in pinMessageToParentChannel()Required in pinToParentChannel()DefaultDescription
messageThreadMessageYesNon/aThreadMessage object you want to pin to the selected parent channel.

Output

TypeDescription
Promise<Channel>Object returning the channel metadata updated with these custom fields:

pinnedMessageTimetoken to mark the timetoken when the message was pinned

pinnedMessageChannelID to mark the channel on which the message was pinned to the parent channel (pinning was performed either directly on the parent channel or on a thread channel).

Errors

Whenever you try to pin the thread message to the removed parent channel, you will receive the Parent channel doesn't exist error.

Basic usage

A thread was created for the last message in the support parent channel. Pin the last message from this thread to the parent channel.

  • pinMessageToParentChannel()

    // reference the "support" channel where the root message for the thread is published
    const channel = await chat.getChannel("support")
    // get the last message on the channel, which is the root message for the thread
    const message = (await channel.getHistory({count: 1})).messages[0]
    // get the thread channel
    const threadChannel = await message.getThread()
    // get the last message on the thread channel
    const threadMessage = (await threadChannel.getHistory({count: 1})).messages[0]
    // pin the message to the parent channel
    const pinnedMessage = await threadChannel.pinMessageToParentChannel(threadMessage)
  • pinToParentChannel()

    // reference the "support" channel where the root message for the thread is published
    const channel = await chat.getChannel("support")
    // get the last message on the channel, which is the root message for the thread
    const message = (await channel.getHistory({count: 1})).messages[0]
    // get the thread channel
    const threadChannel = await message.getThread()
    // get the last message on the thread channel
    const threadMessage = (await threadChannel.getHistory({count: 1})).messages[0]
    // pin the message to the parent channel
    const pinnedMessage = await threadMessage.pinToParentChannel()

Unpin thread message from thread channel

unpinMessage() called on the ThreadChannel object unpins the previously pinned thread message from the thread channel.

icon

Under the hood

Method signature

This method has the following signature:

threadChannel.unpinMessage(): Promise<ThreadChannel>

Input

This method doesn't take any parameters.

Output

TypeDescription
Promise<ThreadChannel>Object returning the thread channel metadata updated with these custom fields:

pinnedMessageTimetoken to mark the timetoken when the message was unpinned

pinnedMessageChannelID to mark the channel on which the message was unpinned from the thread channel (unpinning was performed either directly on the parent channel or on a thread channel).

Basic usage

Unpin the thread message from the thread (channel) created for the last message on the support channel.

// reference the "support" channel where the root message for the thread is published
const channel = await chat.getChannel("support")
// get the last message on the channel, which is the root message for the thread
const message = (await channel.getHistory({count: 1})).messages[0]
// get the thread channel
const threadChannel = message.getThread()
// unpin the message from the thread channel
const unpinnedMessage = await threadChannel.unpinMessage()

Unpin thread message from parent channel

You can unpin the previously pinned thread message from the parent channel with unpinMessageFromParentChannel() and unpinFromParentChannel().

icon

Under the hood

Method signature

These methods have the following signatures:

  • unpinMessageFromParentChannel()

    threadChannel.unpinMessageFromParentChannel(): Promise<Channel>
  • unpinFromParentChannel()

    threadMessage.unpinFromParentChannel(): Promise<Channel>

Input

These methods don't take any parameters.

Output

TypeDescription
Promise<Channel>Object returning the channel metadata updated with these custom fields:

pinnedMessageTimetoken to mark the timetoken when the message was unpinned

pinnedMessageChannelID to mark the channel on which the message was unpinned from the parent channel (unpinning was performed either directly on the parent channel or on a thread channel).

Errors

Whenever you try to unpin the thread message from the removed parent channel, you will receive the Parent channel doesn't exist error.

Basic usage

Unpin the thread message from the support parent channel.

  • unpinMessageFromParentChannel()

    // reference the "support" channel where the root message for the thread is published
    const channel = await chat.getChannel("support")
    // get the last message on the channel, which is the root message for the thread
    const message = (await channel.getHistory({count: 1})).messages[0]
    // get the thread channel
    const threadChannel = message.getThread()
    // unpin the message from the parent channel
    const unpinnedMessage = await threadChannel.unpinFromParentChannel()
  • unpinFromParentChannel()

    // reference the "support" channel where the root message for the thread is published
    const channel = await chat.getChannel("support")
    // get the last message on the channel, which is the root message for the thread
    const message = (await channel.getHistory({count: 1})).messages[0]
    // get the thread channel
    const threadChannel = message.getThread()
    // reference the pinned message
    const threadMessage = (await threadChannel.getHistory({count: 1})).messages[0]
    // unpin the message from the parent channel
    const unpinnedMessage = await threadMessage.unpinFromParentChannel()
Last updated on