On this page

Message threads

Organize conversations into threads for topic-specific discussions.

Benefits:

  • Focus discussions on specific topics
  • Clarify and resolve issues without cross-talk
  • Keep main channel conversations clean

Thread channels have IDs starting with PUBNUB_INTERNAL_THREAD_{channel_id}_{message_id}. Messages with threads have hasThread: true.

ThreadMessage and ThreadChannel extend the base message and channel entities with thread-specific methods.

Interactive demo

See threads and quoted messages in a React app.

Want to implement something similar?

See how to or view the 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:

1message.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.

Sample code

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

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

Create thread with result

createThreadWithResult() creates a thread, sends the first reply, and returns both the thread channel and updated parent message with hasThread: true. Use this when you need immediate confirmation without a separate fetch.

icon

Under the hood

Method signature

This method has the following signature:

1message.createThreadWithResult(
2 text: string,
3 options?: {
4 meta?: object,
5 storeInHistory?: boolean,
6 sendByPost?: boolean,
7 ttl?: number,
8 quotedMessage?: Message,
9 files?: FileList | File[] | { stream: NodeJS.ReadableStream; name: string; mimeType?: string }[],
10 usersToMention?: string[],
11 customPushData?: { [key: string]: string }
12 }
13): Promise<CreateThreadResult>

Input

* required
ParameterDescription
text *
Type: string
Default:
n/a
Text that you want to send as the first message in the thread.
options
Type: object
Default:
n/a
Object containing additional options for the thread message.
 → meta
Type: object
Default:
n/a
Additional details to publish with the request.
 → storeInHistory
Type: boolean
Default:
true
If true, the messages are stored in Message Persistence if enabled.
 → sendByPost
Type: boolean
Default:
false
Use HTTP POST for the request.
 → ttl
Type: number
Default:
n/a
Time (in hours) the message should be stored in Message Persistence. If storeInHistory is true and ttl is 0, the message is stored with no expiry time. If storeInHistory is true and ttl is set to a value, the message is stored with that expiry time. If storeInHistory is false, this parameter is ignored. If not specified, the expiration defaults to the expiry value for the keyset.
 → quotedMessage
Type: Message
Default:
n/a
Object added to a message when you quote another message.
 → files
Type: FileList | File[] | object[]
Default:
n/a
One or multiple files attached to the text message.
 → usersToMention
Type: string[]
Default:
n/a
Array of user IDs to automatically notify with a mention after this message is sent.
 → customPushData
Type: object
Default:
n/a
Additional key-value pairs that will be added to the FCM and/or APNS push messages.

Output

TypeDescription
Promise<CreateThreadResult>
Object containing both the threadChannel and the updated parentMessage.

The returned CreateThreadResult object contains:

PropertyDescription
threadChannel
Type: ThreadChannel
The newly created thread channel for sending and receiving messages.
parentMessage
Type: Message
The updated parent message with hasThread set to true.

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.

Sample code

Create a thread for the last message on the support channel and get the updated parent message.

1// reference the channel with the message
2const channel = await chat.getChannel("support")
3// get the last message on this channel
4const message = (await channel.getHistory({count: 1})).messages[0]
5// create a thread and get the result with updated parent
6const { threadChannel, parentMessage } = await message.createThreadWithResult(
7 "This is the first reply in the thread"
8)
9
10// parentMessage now has hasThread = true without needing to re-fetch
11console.log("Thread created:", threadChannel.id)
12console.log("Parent has thread:", parentMessage.hasThread) // true

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.

Sample code

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

1// reference the channel where you want to send a message
2const channel = await chat.getChannel("support")
3// get the last message on the channel to which you want to reply in a thread
4const message = (await channel.getHistory({count: 1})).messages[0]
5// get the thread channel
6const threadChannel = message.getThread()
7// send a message in the thread
8threadChannel.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:

1message.getThread(): Promise<ThreadChannel>

Input

This method doesn't take any parameters.

Output

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

Sample code

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

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

Check if message starts thread

hasThread indicates if a message starts a thread.

Method signature

This method has the following signature:

1message.hasThread: boolean

Properties

PropertyDescription
hasThread
Type: boolean
Info on whether the message already starts a thread or not.

Sample code

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

1// reference the "message" that you're interested in
2const message = await channel.getHistory({
3 startTimetoken: "16200000000000000",
4 endTimetoken: "162000000000000001",
5})
6
7// check if the message starts a thread
8message.hasThread

Get thread updates

streamUpdatesOn() on ThreadMessage receives updates when thread messages or reactions change.

The callback fires whenever messages are added, edited, deleted, or reactions change. Returns an unsubscribe function.

Stream update behavior

streamUpdatesOn() returns the complete list of monitored thread messages on each change.

Method signature

icon

Under the hood


This method takes the following parameters:

1static ThreadMessage.streamUpdatesOn(
2 threadMessages: ThreadMessage[]
3 callback: (threadMessages: ThreadMessage[]) => unknown
4): () => void

Input

* required
ParameterDescription
threadMessages *
Type: ThreadMessage[]
Default:
n/a
Array of ThreadMessage objects for which you want to get updates on changed message threads or related message reactions.
callback *
Type: n/a
Default:
n/a
Callback 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.
 → threadMessages *
Type: ThreadMessage[]
Default:
n/a
Returned array of ThreadMessage objects with the updated message threads or related reactions.

Output

TypeDescription
() => void
Function 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.

Sample code

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

1const channel = await chat.getChannel("support")
2const { threadMessages } = await channel.getHistory()
3ThreadMessages.streamUpdatesOn(threadMessages, (threadMessages) => {
4 // The callback receives the complete list of all thread messages you're monitoring
5 // (including all reactions) each time any change occurs.
6 console.log("Updated messages: ", threadMessages)
7})

Other examples

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

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

Get historical thread message

getHistory() on threadChannel fetches historical thread messages.

icon

Under the hood

Method signature

This method takes the following parameters:

1threadChannel.getHistory({
2 startTimetoken?: string;
3 endTimetoken?: string;
4 count?: number;
5}): Promise<{
6 messages: ThreadMessage[];
7 isMore: boolean;
8}>

Input

* required
ParameterDescription
startTimetoken
Type: string
Default:
n/a
Timetoken delimiting the start of a time slice (exclusive) to pull thread messages from. For details, refer to the Fetch History section.
endTimetoken
Type: string
Default:
n/a
Timetoken delimiting the end of a time slice (inclusive) to pull thread messages from. For details, refer to the Fetch History section.
count
Type: number
Default:
25
Number 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

ParameterDescription
Promise<>
Type: object
Returned object containing two fields: messages and isMore.
 → messages
Type: ThreadMessage[]
Array listing the requested number of historical thread Message objects.
 → isMore
Type: boolean
Whether 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.

Sample code

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

1// reference the "support" channel
2const channel = await chat.getChannel("support")
3// get the last message on the channel, which is the root message for the thread
4const message = (await channel.getHistory({count: 1})).messages[0]
5// get the thread channel
6const threadChannel = message.getThread()
7// fetch the required historical messages
8threadMessages = await threadChannel.getHistory(
9 {
10 startTimetoken: "15343325214676133",
11 count: 10
12 }
13)

Remove thread

removeThread() removes a thread (channel) for a selected message.

icon

Under the hood

Method signature

This method has the following signature:

1message.removeThread(): Promise<any>

Input

This method doesn't take any parameters.

Output

TypeDescription
Promise<any>
Returned object with a value of any type.

Errors

Whenever you try to remove a thread that doesn't exist, you'll get the There is no thread to be deleted error.

Sample code

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

1// reference the channel with the message
2const channel = await chat.getChannel("support");
3// get the last message on this channel
4const message = (await channel.getHistory({count: 1})).messages[0];
5// remove the thread for this message
6await message.removeThread();

Pin thread message to thread channel

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

icon

Under the hood

Method signature

This method takes the following parameters:

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

Input

* required
ParameterDescription
message *
Type: ThreadMessage
Default:
n/a
ThreadMessage 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).

Sample code

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

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

Pin thread message to parent channel

pinMessageToParentChannel() (on ThreadChannel) and pinToParentChannel() (on ThreadMessage) pin a thread message to the parent channel.

icon

Under the hood

Method signature

These methods take the following parameters:

  • pinMessageToParentChannel()

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

    1threadMessage.pinToParentChannel(): Promise<Channel>

Input

ParameterRequired in pinMessageToParentChannel()Required in pinToParentChannel()Description
message
Type: ThreadMessage
Default:
n/a
Yes
No
ThreadMessage 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.

Sample code

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()

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

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

Unpin thread message from thread channel

unpinMessage() on ThreadChannel unpins the pinned thread message.

icon

Under the hood

Method signature

This method has the following signature:

1threadChannel.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).

Sample code

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

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

Unpin thread message from parent channel

unpinMessageFromParentChannel() (on ThreadChannel) and unpinFromParentChannel() (on ThreadMessage) unpin a thread message from the parent channel.

icon

Under the hood

Method signature

These methods have the following signatures:

  • unpinMessageFromParentChannel()

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

    1threadMessage.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.

Sample code

Unpin the thread message from the support parent channel.

  • unpinMessageFromParentChannel()

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

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