---
source_url: https://www.pubnub.com/docs/chat/unreal-chat-sdk/build/features/messages/threads
title: Message threads
updated_at: 2026-06-19T11:35:28.401Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Message threads

Organize conversations into threads for topic-specific discussions.

##### Usage in Blueprints and C++

You can use PubNub's functionality via Blueprints or directly in C++ code.

* In Blueprints, you can access PubNub from any Widget or Actor. Start by [initializing chat](https://www.pubnub.com/docs/chat/unreal-chat-sdk/build/configuration#initialize-pubnub-chat) using `InitChat` on `UPubnubChatSubsystem`. Afterwards, you can use the returned `UPubnubChat` object reference to call all Chat SDK functions.

:::warning Blueprint functions
The Blueprints provided in the documentation show how you can structure your application and may contain utility methods and elements like buttons that are not part of the Unreal Chat SDK and are meant to serve as guidance.
:::

* In C++, you can use UPubnubChatSubsystem as any other Game Instance Subsystem. #include "Kismet/GameplayStatics.h" #include "Engine/GameInstance.h" #include "PubnubChatSubsystem.h" // ACTION REQUIRED: Replace ASample_ChatSubsystem with name of your Actor class void ASample_ChatSubsystem::InitChatSample() { // Get PubnubChatSubsystem from GameInstance UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this); UPubnubChatSubsystem* PubnubChatSubsystem = GameInstance->GetSubsystem<UPubnubChatSubsystem>(); // Initialize Chat - InitChat may fail under some conditions so make sure to check the Result for errors before using the Chat FPubnubChatInitChatResult InitChatResult = PubnubChatSubsystem->InitChat(TEXT("demo"), TEXT("demo"), TEXT("Player_001")); UPubnubChat* PubnubChat = InitChatResult.Chat; } Chat now allows you to call all Chat SDK functions, for example, Chat->GetChannel("my_channel").

:::note Asynchronous and synchronous method execution
Most PubNub Unreal SDK methods are available in both asynchronous and synchronous variants.
* Asynchronous methods (Async suffix) return void and take an optional delegate parameter that fires when the operation completes. 1Message->CreateThreadAsync(OnCreateThreadResponseDelegate); You can also use native callbacks that accept lambdas instead of dynamic delegates. Native callback types have the Native suffix (for example, FOnPubnubChatThreadChannelResponseNative).
* Synchronous methods (no suffix) block the main game thread until the operation completes and return a result struct directly. 1FPubnubChatThreadChannelResult Result = Message->CreateThread();
:::

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](https://www.pubnub.com/docs/chat/unreal-chat-sdk/learn/chat-entities/thread-message) and [ThreadChannel](https://www.pubnub.com/docs/chat/unreal-chat-sdk/learn/chat-entities/thread-channel) extend the base `message` and `channel` entities with thread-specific methods.

## Create thread

`CreateThread()` creates a thread (channel) for a selected message.

### Method signature

#### Blueprint

#### C++ / Input parameters

```cpp
Message->CreateThread();
```

#### Output

| Field | Type | Description |
| --- | --- | --- |
| `Result` | `FPubnubChatOperationResult` | Operation result with `Error` (bool) and `ErrorMessage` (FString). |
| `ThreadChannel` | `UPubnubChatThreadChannel*` | Object returning the thread channel metadata for the message updated with the `hasThread` parameter (and a `threadRootId` action type underneath). |

### Sample code

:::tip Reference code
This example is a self-contained code snippet ready to be run. Set up your Unreal project and follow the instructions in the lines marked with `ACTION REQUIRED` before running the code. Use it as a reference when working with other examples in this document.
:::

Create a thread for a message asynchronously.

###### C++

###### Actor.h

```cpp
// blueprint.mtz1c0z9
UFUNCTION(BlueprintCallable, Category = "PubnubChat|Samples|ChatMessage")
void CreateThreadSample();

UFUNCTION()
void OnCreateThreadResponse(const FPubnubChatThreadChannelResult& Result);
```

###### Actor.cpp

```cpp
// ACTION REQUIRED: Replace ASample_ChatMessage with name of your Actor class
void ASample_ChatMessage::CreateThreadSample()
{
	// snippet.hide
	UPubnubChatMessage* Message = nullptr;
	// snippet.show

	// Assumes Message is a valid UPubnubChatMessage

	// Callback for when the operation completes (returns thread channel)
	FOnPubnubChatThreadChannelResponseNative Callback;
	// ACTION REQUIRED: Replace ASample_ChatMessage with name of your Actor class
	Callback.BindUObject(this, &ASample_ChatMessage::OnCreateThreadResponse);
	Message->CreateThreadAsync(Callback);
}

// ACTION REQUIRED: Replace ASample_ChatMessage with name of your Actor class
void ASample_ChatMessage::OnCreateThreadResponse(const FPubnubChatThreadChannelResult& Result)
{
	if (Result.Result.Error) { return; }
	// At this point the thread channel exists locally; it is created on the server when the first reply is sent
	UPubnubChatThreadChannel* ThreadChannel = Result.ThreadChannel;
	
	// Send the first reply to create the thread on the server
	ThreadChannel->SendText(TEXT("First thread message"));
}
```

###### Blueprint

### Other examples

#### Create thread channel from Chat object

###### Actor.h

```cpp
UFUNCTION(BlueprintCallable, Category = "PubnubChat|Samples|Chat|Threads")
void CreateThreadChannelSample();

UFUNCTION()
void OnCreateThreadChannelResponse(const FPubnubChatThreadChannelResult& Result);
```

###### Actor.cpp

```cpp
#include "PubnubChatThreadChannel.h"

// ACTION REQUIRED: Replace ASample_Chat with name of your Actor class
void ASample_Chat::CreateThreadChannelSample()
{
	// snippet.hide
	UPubnubChat* Chat = nullptr;
	// snippet.show

	// Assumes Chat is a valid and initialized instance of UPubnubChat
	
	// ParentMessage: the message to create a thread on (e.g. from channel listeners or from history)
	UPubnubChatMessage* ParentMessage = nullptr;

	FOnPubnubChatThreadChannelResponseNative Callback;
	// ACTION REQUIRED: Replace ASample_Chat with name of your Actor class
	Callback.BindUObject(this, &ASample_Chat::OnCreateThreadChannelResponse);
	Chat->CreateThreadChannelAsync(ParentMessage, Callback);
}

// ACTION REQUIRED: Replace ASample_Chat with name of your Actor class
void ASample_Chat::OnCreateThreadChannelResponse(const FPubnubChatThreadChannelResult& Result)
{
	if (Result.Result.Error) { return; }
	// At this point the thread channel exists locally; it is created on the server when the first reply is sent
	UPubnubChatThreadChannel* ThreadChannel = Result.ThreadChannel;
	
	// Send the first reply to create the thread on the server
	ThreadChannel->SendText(TEXT("First thread message"));
}
```

## 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](https://www.pubnub.com/docs/chat/unreal-chat-sdk/build/features/messages/send-receive) section for details.

## Get thread

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

### Method signature

#### Blueprint

#### C++ / Input parameters

```cpp
Message->GetThread();
```

#### Output

| Field | Type | Description |
| --- | --- | --- |
| `Result` | `FPubnubChatOperationResult` | Operation result with `Error` (bool) and `ErrorMessage` (FString). |
| `ThreadChannel` | `UPubnubChatThreadChannel*` | Object returning the thread channel metadata. |

### Sample code

Get the thread channel created from a message.

#### C++

```cpp
// ACTION REQUIRED: Replace ASample_ChatMessage with name of your Actor class
void ASample_ChatMessage::GetThreadSample()
{
	// snippet.hide
	UPubnubChatMessage* Message = nullptr;
	// snippet.show

	// Assumes Message is a valid UPubnubChatMessage

	// Callback for when the operation completes (returns thread channel if it exists)
	FOnPubnubChatThreadChannelResponseNative Callback;
	// ACTION REQUIRED: Replace ASample_ChatMessage with name of your Actor class
	Callback.BindUObject(this, &ASample_ChatMessage::OnGetThreadResponse);
	Message->GetThreadAsync(Callback);
}

// ACTION REQUIRED: Replace ASample_ChatMessage with name of your Actor class
void ASample_ChatMessage::OnGetThreadResponse(const FPubnubChatThreadChannelResult& Result)
{
	if (Result.Result.Error) { return; }
	UPubnubChatThreadChannel* ThreadChannel = Result.ThreadChannel;
}
```

#### Blueprint

### Other examples

#### Get thread channel from Chat object

###### Actor.h

```cpp
UFUNCTION(BlueprintCallable, Category = "PubnubChat|Samples|Chat|Threads")
void GetThreadChannelSample();

UFUNCTION()
void OnGetThreadChannelResponse(const FPubnubChatThreadChannelResult& Result);
```

###### Actor.cpp

```cpp
// ACTION REQUIRED: Replace ASample_Chat with name of your Actor class
void ASample_Chat::GetThreadChannelSample()
{
	// snippet.hide
	UPubnubChat* Chat = nullptr;
	// snippet.show

	// Assumes Chat is a valid and initialized instance of UPubnubChat
	
	// ParentMessage: the message whose thread channel to fetch (e.g. from channel listeners or history)
	UPubnubChatMessage* ParentMessage = nullptr;

	FOnPubnubChatThreadChannelResponseNative Callback;
	// ACTION REQUIRED: Replace ASample_Chat with name of your Actor class
	Callback.BindUObject(this, &ASample_Chat::OnGetThreadChannelResponse);
	Chat->GetThreadChannelAsync(ParentMessage, Callback);
}

// ACTION REQUIRED: Replace ASample_Chat with name of your Actor class
void ASample_Chat::OnGetThreadChannelResponse(const FPubnubChatThreadChannelResult& Result)
{
	if (Result.Result.Error) { return; }
	UPubnubChatThreadChannel* ThreadChannel = Result.ThreadChannel;
}
```

## Utility getter function

You can use the `HasThread()` method to check if a given message starts a thread.

### Method signature

#### Blueprint

#### C++ / Input parameters

```cpp
Message->HasThread();
```

#### Output

| Field | Type | Description |
| --- | --- | --- |
| `Result` | `FPubnubChatOperationResult` | Operation result with `Error` (bool) and `ErrorMessage` (FString). |
| `HasThread` | `bool` | Info on whether the message already starts a thread or not. |

### Sample code

Check if a message starts a thread.

#### C++

```cpp
// ACTION REQUIRED: Replace ASample_ChatMessage with name of your Actor class
void ASample_ChatMessage::HasThreadSample()
{
	// snippet.hide
	UPubnubChatMessage* Message = nullptr;
	// snippet.show

	// Assumes Message is a valid UPubnubChatMessage

	FPubnubChatHasThreadResult Result = Message->HasThread();
	if (Result.Result.Error) { return; }
	bool bHasThread = Result.HasThread;
}
```

#### Blueprint

#### Get parent channel ID (ThreadChannel)

#### Get parent message

#### Get parent channel ID (ThreadMessage)

## Get thread updates

You can receive updates when specific thread messages and related [message reactions](https://www.pubnub.com/docs/chat/unreal-chat-sdk/build/features/messages/reactions) are added, edited, or removed on other clients. Thread messages use the same streaming mechanism as regular messages.

Bind each thread message's `OnUpdated` delegate before calling `StreamUpdates()`, then call `StopStreamingUpdates()` to stop. Use `StreamUpdatesOn()` to start streaming for multiple thread messages at once.

:::note Stream update behavior
Each thread message fires its own `OnUpdated` delegate individually with `(FString Timetoken, FPubnubChatMessageData MessageData)`.
:::

### Method signature

```cpp
ThreadMessage->StreamUpdates();
UPubnubChatMessage::StreamUpdatesOn(TArray<UPubnubChatMessage*> Messages);
ThreadMessage->StopStreamingUpdates();
```

#### Output

| Type | Description |
| --- | --- |
| `FPubnubChatOperationResult` | Result of the operation. Check `Error` for failure. |

### Sample code

:::info
The following sample demonstrates how to listen for **new thread messages** (replies) using `Connect()`/`Disconnect()`, which is a related but distinct operation from `StreamUpdates()`. `StreamUpdates()` streams changes to *existing* messages (edits, reactions, deletions), while `Connect()` receives *new* messages posted to the thread.
:::

Listen for new thread messages on a thread channel.

###### C++

```cpp
// ACTION REQUIRED: Replace ASample_ChatThreadChannel with name of your Actor class
void ASample_ChatThreadChannel::ListenThreadMessagesSample()
{
	// snippet.hide
	UPubnubChatThreadChannel* ThreadChannel = nullptr;
	// snippet.show

	// Assumes ThreadChannel is a valid UPubnubChatThreadChannel

	// Bind to receive new thread messages (replies)
	ThreadChannel->OnThreadMessageReceivedNative.AddUObject(this, &ASample_ChatThreadChannel::OnThreadMessageReceived);

	// Start listening for thread messages
	ThreadChannel->ConnectAsync();

	// After some time when receiving messages is not needed anymore - disconnect
	ThreadChannel->Disconnect();
}

// ACTION REQUIRED: Replace ASample_ChatThreadChannel with name of your Actor class
void ASample_ChatThreadChannel::OnThreadMessageReceived(UPubnubChatThreadMessage* ThreadMessage)
{
	/* e.g. display thread reply in UI */
}
```

###### Blueprint

### OnThreadMessageReceived delegate

When a `ThreadChannel` is connected (via `Connect()`), incoming thread messages are delivered through the `OnThreadMessageReceived` delegate. Always bind `OnThreadMessageReceived` (type `FOnPubnubChatThreadMessageReceived`, parameter `UPubnubChatThreadMessage*`) before calling `Connect()` to handle new replies.

| Delegate | Type | Payload |
| --- | --- | --- |
| `OnThreadMessageReceived` | `FOnPubnubChatThreadMessageReceived` | `UPubnubChatThreadMessage*` -- the received thread message object. |

```cpp
ThreadChannel->OnThreadMessageReceived.AddDynamic(this, &AMyActor::OnNewThreadMessage);

ThreadChannel->Connect();

// To stop receiving:
// ThreadChannel->Disconnect();
```

## Get historical thread message

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

### Method signature

This method takes the following parameters:

```cpp
ThreadChannel->GetThreadHistory(
    FString StartTimetoken,
    FString EndTimetoken,
    int Count = 25
);
```

| Parameter | Description |
| --- | --- |
| `StartTimetoken`Type: `FString`Default: n/a | Start timetoken (inclusive) for the history range. Must be higher (newer) than `EndTimetoken`. |
| `EndTimetoken`Type: `FString`Default: n/a | End timetoken (inclusive) for the history range. Must be lower (older) than `StartTimetoken`. |
| `Count`Type: `int`Default: `25` | Maximum number of historical thread messages to return in a single call. |

#### Output

| Field | Type | Description |
| --- | --- | --- |
| `Result` | `FPubnubChatOperationResult` | Operation result with `Error` (bool) and `ErrorMessage` (FString). |
| `ThreadMessages` | `TArray<UPubnubChatThreadMessage*>` | Array of historical thread [Message objects](https://www.pubnub.com/docs/chat/unreal-chat-sdk/learn/chat-entities/message). |
| `IsMore` | `bool` | `true` when the returned count equals `Count`, indicating more messages may exist in the range. |

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

### Sample code

From the thread created for a message in the `support` channel, fetch historical thread messages.

```cpp
// ACTION REQUIRED: Replace ASample_ChatThreadChannel with name of your Actor class
void ASample_ChatThreadChannel::GetThreadHistorySample()
{
	// snippet.hide
	UPubnubChatThreadChannel* ThreadChannel = nullptr;
	// snippet.show

	// Assumes ThreadChannel is a valid UPubnubChatThreadChannel

	// Start timetoken (inclusive); must be higher (newer) than EndTimetoken (17-digit PubNub timetoken)
	FString StartTimetoken = TEXT("17800000000000000");
	// End timetoken (inclusive); must be lower (older) than StartTimetoken
	FString EndTimetoken = TEXT("17200000000000000");

	// Callback for when the operation completes (returns thread messages and IsMore)
	FOnPubnubChatGetThreadHistoryResponseNative Callback;
	// ACTION REQUIRED: Replace ASample_ChatThreadChannel with name of your Actor class
	Callback.BindUObject(this, &ASample_ChatThreadChannel::OnGetThreadHistoryResponse);
	ThreadChannel->GetThreadHistoryAsync(StartTimetoken, EndTimetoken, Callback);
}

// ACTION REQUIRED: Replace ASample_ChatThreadChannel with name of your Actor class
void ASample_ChatThreadChannel::OnGetThreadHistoryResponse(const FPubnubChatGetThreadHistoryResult& Result)
{
	if (Result.Result.Error) { return; }
	TArray<UPubnubChatThreadMessage*> ThreadMessages = Result.ThreadMessages;
	bool bIsMore = Result.IsMore;
}
```

## Remove thread

`RemoveThread()` removes a thread (channel) for a selected message.

### Method signature

#### Blueprint

#### C++ / Input parameters

```cpp
Message->RemoveThread();
```

#### Output

| Type | Description |
| --- | --- |
| `FPubnubChatOperationResult` | Result of the operation. Check `Error` and `ErrorMessage` for failure. |

### Sample code

Remove a thread for a message.

#### C++

```cpp
// ACTION REQUIRED: Replace ASample_ChatMessage with name of your Actor class
void ASample_ChatMessage::RemoveThreadSample()
{
	// snippet.hide
	UPubnubChatMessage* Message = nullptr;
	// snippet.show

	// Assumes Message is a valid UPubnubChatMessage

	// Remove the thread from this message
	Message->RemoveThreadAsync(nullptr);
}
```

#### Blueprint

### Other examples

#### Remove thread channel from Chat object

###### Actor.h

```cpp
UFUNCTION(BlueprintCallable, Category = "PubnubChat|Samples|Chat|Threads")
void RemoveThreadChannelSample();

UFUNCTION()
void OnRemoveThreadChannelResponse(const FPubnubChatOperationResult& Result);
```

###### Actor.cpp

```cpp
// ACTION REQUIRED: Replace ASample_Chat with name of your Actor class
void ASample_Chat::RemoveThreadChannelSample()
{
	// snippet.hide
	UPubnubChat* Chat = nullptr;
	// snippet.show

	// Assumes Chat is a valid and initialized instance of UPubnubChat
	
	// ParentMessage: the message whose thread to remove (e.g. from channel listeners or history)
	UPubnubChatMessage* ParentMessage = nullptr;

	FOnPubnubChatOperationResponseNative Callback;
	// ACTION REQUIRED: Replace ASample_Chat with name of your Actor class
	Callback.BindUObject(this, &ASample_Chat::OnRemoveThreadChannelResponse);
	Chat->RemoveThreadChannelAsync(ParentMessage, Callback);
}

// ACTION REQUIRED: Replace ASample_Chat with name of your Actor class
void ASample_Chat::OnRemoveThreadChannelResponse(const FPubnubChatOperationResult& Result)
{
	if (Result.Error) { return; }
	// Thread removed
}
```

## Pin thread message to parent channel

You can pin a selected thread message to the parent channel with `ThreadChannel->PinMessageToParentChannel()` and `ThreadMessage->PinMessageToParentChannel()`. 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.

### Method signature

#### Blueprint

#### C++ / Input parameters

* ThreadChannel->PinMessageToParentChannel() 1ThreadChannel->PinMessageToParentChannel(UPubnubChatThreadMessage* ThreadMessage);
* ThreadMessage->PinMessageToParentChannel() 1ThreadMessage->PinMessageToParentChannel();

| Parameter | Required in `ThreadChannel->PinMessageToParentChannel()` | Required in `ThreadMessage->PinMessageToParentChannel()` | Description |
| --- | --- | --- | --- |
| `ThreadMessage`Type: `UPubnubChatThreadMessage*`Default: n/a | Yes | No | [ThreadMessage object](https://www.pubnub.com/docs/chat/unreal-chat-sdk/learn/chat-entities/thread-message) you want to pin to the selected parent channel. |

#### Output

| Type | Description |
| --- | --- |
| `FPubnubChatOperationResult` | Result of the operation. Check `Error` and `ErrorMessage` for failure. |

### Sample code

Pin a thread message to the parent channel.

#### C++

```cpp
// ACTION REQUIRED: Replace ASample_ChatThreadChannel with name of your Actor class
void ASample_ChatThreadChannel::PinMessageToParentChannelSample()
{
	// snippet.hide
	UPubnubChatThreadChannel* ThreadChannel = nullptr;
	// snippet.show

	// Assumes ThreadChannel is a valid UPubnubChatThreadChannel

	// Thread message to pin to the parent channel
	UPubnubChatThreadMessage* ThreadMessage = nullptr;

	// Pin the thread message on the parent channel
	ThreadChannel->PinMessageToParentChannelAsync(ThreadMessage, nullptr);
}
```

#### Blueprint

### Other examples

#### Pin thread message to parent channel from ThreadMessage

###### Actor.h

```cpp
UFUNCTION(BlueprintCallable, Category = "PubnubChat|Samples|ChatThreadMessage")
void PinMessageToParentChannelSample();
```

###### Actor.cpp

```cpp
// ACTION REQUIRED: Replace ASample_ChatThreadMessage with name of your Actor class
void ASample_ChatThreadMessage::PinMessageToParentChannelSample()
{
	// snippet.hide
	UPubnubChatThreadMessage* ThreadMessage = nullptr;
	// snippet.show

	// Assumes ThreadMessage is a valid UPubnubChatThreadMessage

	// Pin this thread message to the parent channel
	ThreadMessage->PinMessageToParentChannelAsync(nullptr);
}
```

## Unpin thread message from parent channel

You can unpin the previously pinned thread message from the parent channel with `ThreadChannel->UnpinMessageFromParentChannel()` and `ThreadMessage->UnpinMessageFromParentChannel()`.

### Method signature

#### Blueprint

#### C++ / Input parameters

* ThreadChannel->UnpinMessageFromParentChannel() 1ThreadChannel->UnpinMessageFromParentChannel();
* ThreadMessage->UnpinMessageFromParentChannel() 1ThreadMessage->UnpinMessageFromParentChannel();

#### Output

| Type | Description |
| --- | --- |
| `FPubnubChatOperationResult` | Result of the operation. Check `Error` and `ErrorMessage` for failure. |

### Sample code

Unpin the thread message from the parent channel.

#### C++

```cpp
// ACTION REQUIRED: Replace ASample_ChatThreadChannel with name of your Actor class
void ASample_ChatThreadChannel::UnpinMessageFromParentChannelSample()
{
	// snippet.hide
	UPubnubChatThreadChannel* ThreadChannel = nullptr;
	// snippet.show

	// Assumes ThreadChannel is a valid UPubnubChatThreadChannel

	// Unpin the currently pinned message from the parent channel
	ThreadChannel->UnpinMessageFromParentChannelAsync(nullptr);
}
```

#### Blueprint

### Other examples

#### Unpin thread message from parent channel from ThreadMessage

###### Actor.h

```cpp
UFUNCTION(BlueprintCallable, Category = "PubnubChat|Samples|ChatThreadMessage")
void UnpinMessageFromParentChannelSample();
```

###### Actor.cpp

```cpp
// ACTION REQUIRED: Replace ASample_ChatThreadMessage with name of your Actor class
void ASample_ChatThreadMessage::UnpinMessageFromParentChannelSample()
{
	// snippet.hide
	UPubnubChatThreadMessage* ThreadMessage = nullptr;
	// snippet.show

	// Assumes ThreadMessage is a valid UPubnubChatThreadMessage

	// Unpin this thread message from the parent channel
	ThreadMessage->UnpinMessageFromParentChannelAsync(nullptr);
}
```