Unreal Chat SDK 1.0.0 migration guide
The 1.0.0 release of the Unreal Chat SDK rebuilds the SDK on top of the PubNub Unreal Engine Core SDK (PubnubLibrary) and removes the dependency on the bundled C++ Chat SDK (cpp-chat.dll). The API surface is rearchitected: all network-calling methods now return structured result types, every method gains an asynchronous variant, and real-time listeners use entity-owned delegate properties instead of callback parameters.
This guide is for developers who use Unreal Chat SDK 0.x.x in existing applications.
If your application uses 0.x.x, it will not compile against 1.0.0 without changes. This guide summarizes the differences between the versions and shows how to migrate to Unreal Chat SDK 1.0.0.
Most notable changes include:
- New dependency: Unreal Chat SDK
0.x.xwas a self-contained plugin. Version1.0.0requires the PubNub Unreal Engine Core SDK (PubnubLibrary) as a separate plugin installed alongside the Chat SDK. - Type renames: Most classes and structs gained a
Chatinfix (UPubnubUser→UPubnubChatUser,FPubnubMessageAction→FPubnubChatMessageAction, and others). - Result structs: Every method now returns a typed result struct wrapping
FPubnubChatOperationResult(withErrorandErrorMessagefields) alongside the domain data. No more raw pointer returns or silentnullptron failure. - Sync/Async split: Every network method is available in both a synchronous blocking variant and an
Asyncvariant that takes a delegate callback. - Entity-first delegates: Real-time listeners use multicast delegate properties on entity objects. Bind delegates before calling the streaming method; stop with an explicit
StopStreaming*()call. - Multiple chat instances: The subsystem now supports multiple simultaneous chat sessions keyed by
UserID. - Synced objects: Entity objects (channels, users, memberships, messages) are now synchronized by ID. All variables that reference the same entity ID share the latest locally known data — updating one updates all.
Differences between 0.x.x and 1.0.0
See the major differences between the versions:
| Feature/Method | 0.x.x | 1.0.0 |
|---|---|---|
| Core dependency | Self-contained plugin (no additional dependency) | Requires PubNub Unreal Engine Core SDK plugin (PubnubLibrary) |
| Method return types | Raw pointer or void | Typed result struct (e.g. FPubnubChatUserResult) containing FPubnubChatOperationResult |
| Async methods | Not available | *Async suffix variants added for every network method |
| Real-time streaming | Stream*(Callback) returns UPubnubCallbackStop* | Stream*() (no callback); bind OnUpdated/OnXxx delegate; stop with StopStreaming*() |
Join behavior | Join() also calls Connect() internally | Join() and Connect() are separate — call both to receive messages after joining |
| Read receipts | StreamReadReceipts() returns a list of UserIDs (limited to 100 members) | Split into FetchReadReceipts() (paginated snapshot with timetokens) and StreamReadReceipts() (per-user real-time updates) |
| Soft/hard delete | DeleteMessage() and DeleteMessageHard() | Delete(bool Soft = false) |
Forward parameter | Message->Forward(FString ChannelID) | Message->Forward(UPubnubChatChannel* Channel) |
GetUsers / GetChannels parameter order | (Filter, Sort, Limit, Page) | (Limit, Filter, Sort, Page) — Sort type changed to FPubnubGetAllSort |
SetRestrictions on Chat | Chat->SetRestrictions(FString UserID, FString ChannelID, FPubnubRestriction) | Chat->SetRestrictions(FPubnubChatRestriction) — UserID and ChannelID now inside the struct |
Subsystem InitChat return | UPubnubChat* | FPubnubChatInitChatResult containing .Chat and error info |
Subsystem GetChat / DestroyChat | No arguments | Require FString UserID argument |
| Multiple chat instances | Not supported | Supported — keyed by UserID |
MessageDraft change listener | AddChangeListener() / AddChangeListenerWithSuggestions() | Bind OnMessageDraftUpdated / OnMessageDraftUpdatedWithSuggestions delegate properties |
| Object identity | Independent snapshots. Two variables with the same entity ID can hold different data simultaneously | Synchronized by ID. All references to the same entity ID share the latest locally known data. |
Join and Connect
Important change
This change affects any code that calls Join() and then listens for messages. Update it before releasing to users.
In 0.x.x, calling Join() on a channel also subscribed to receive messages on that channel automatically. In 1.0.0, Join() creates the membership server-side but does not subscribe. Call Connect() separately after Join() to start receiving messages.
- 0.x.x
- 1.0.0
1// Join implicitly started message delivery
2FPubnubChatOperationResult Result = Channel->Join();
3
4// Messages arrived via the channel delegate
1// Join creates the membership
2FPubnubChatJoinResult JoinResult = Channel->Join();
3if (JoinResult.Result.Error) { return; }
4
5// Connect separately to start receiving messages
6Channel->OnMessageReceivedNative.AddUObject(this, &AMyActor::OnMessageReceived);
7FPubnubChatOperationResult ConnectResult = Channel->Connect();
Dependency
Unreal Chat SDK 0.x.x was a self-contained plugin — no additional downloads were needed. Version 1.0.0 depends on the PubNub Unreal Engine Core SDK (PubnubLibrary).
To install the Core SDK:
- Download the PubNub Unreal Engine Core SDK and place it in your project's
Pluginsfolder alongside the Chat SDK plugin. - Delete the
IntermediateandBinariesfolders from your project. - Rebuild the project.
No changes to your .Build.cs file are required.
Type renames
Rename all entity class references. The Chat infix was added to distinguish Chat SDK types from the underlying UE Core SDK types.
| Old type | New type |
|---|---|
UPubnubUser | UPubnubChatUser |
UPubnubChannel | UPubnubChatChannel |
UPubnubMessage | UPubnubChatMessage |
UPubnubMembership | UPubnubChatMembership |
UPubnubThreadChannel | UPubnubChatThreadChannel |
UPubnubAccessManager | UPubnubChatAccessManager |
FPubnubMessageAction | FPubnubChatMessageAction |
FPubnubRestriction | FPubnubChatRestriction |
FPubnubEvent | FPubnubChatEvent |
FPubnubSendTextParams | FPubnubChatSendTextParams |
FPubnubCreatedChannelWrapper | FPubnubChatCreateGroupConversationResult / FPubnubChatCreateDirectConversationResult |
FPubnubChannelsResponseWrapper | FPubnubChatGetChannelsResult |
FPubnubUsersResponseWrapper | FPubnubChatGetUsersResult |
FPubnubEventsHistoryWrapper | FPubnubChatEventsResult |
Also rename the CustomDataJson field to Custom on FPubnubChatChannelData, FPubnubChatUserData, and FPubnubChatMembershipData:
- 0.x.x
- 1.0.0
1FPubnubChatChannelData Data;
2Data.CustomDataJson = TEXT("{\"color\":\"blue\"}");
1FPubnubChatChannelData Data;
2Data.Custom = TEXT("{\"color\":\"blue\"}");
Update enum value names. All enum values now use PascalCase after the prefix:
| Old value | New value |
|---|---|
PCET_TYPING | PCET_Typing |
PCET_CUSTOM | PCET_Custom |
PCS_Connected | PCCS_ConnectionOnline |
PMAT_Reaction | PCMAT_Reaction |
PMAT_Edited | PCMAT_Edited |
PMAT_Deleted | PCMAT_Deleted |
Result structs
Every method that previously returned a raw pointer or void now returns a typed result struct. The struct always contains a Result field of type FPubnubChatOperationResult with Error (bool) and ErrorMessage (FString). Check Error before accessing the payload.
- 0.x.x
- 1.0.0
1UPubnubUser* User = Chat->CreateUser("user_alice", Data);
2if (!User)
3{
4 // error only visible in logs
5}
1FPubnubChatUserResult Result = Chat->CreateUser("user_alice", Data);
2if (Result.Result.Error)
3{
4 UE_LOG(LogTemp, Error, TEXT("%s"), *Result.Result.ErrorMessage);
5 return;
6}
7UPubnubChatUser* User = Result.User;
Methods that previously returned void now return FPubnubChatOperationResult:
- 0.x.x
- 1.0.0
1Channel->SendText(TEXT("Hello"));
2// no way to know if it succeeded
1FPubnubChatOperationResult Result = Channel->SendText(TEXT("Hello"));
2if (Result.Error)
3{
4 UE_LOG(LogTemp, Error, TEXT("%s"), *Result.ErrorMessage);
5}
Sync and Async methods
Every network method is now available in two variants. Use the synchronous form when you need the result immediately; use the Async form to avoid blocking the game thread.
- Synchronous (blocking)
- Asynchronous (non-blocking)
1// Blocks until complete. Returns result struct directly.
2FPubnubChatUserResult Result = Chat->GetUser("user_alice");
3if (!Result.Result.Error)
4{
5 UPubnubChatUser* User = Result.User;
6}
1// Returns void immediately. Callback fires on the game thread when done.
2Chat->GetUserAsync("user_alice", OnGetUserDelegate);
3
4// Or with a native lambda:
5FOnPubnubChatUserResponseNative Callback;
6Callback.BindLambda([](const FPubnubChatUserResult& Result)
7{
8 if (!Result.Result.Error)
9 {
10 UPubnubChatUser* User = Result.User;
11 }
12});
13Chat->GetUserAsync("user_alice", Callback);
Multiple chat instances
The subsystem now supports multiple simultaneous chat sessions, one per UserID. Update all InitChat, GetChat, and DestroyChat calls.
- 0.x.x
- 1.0.0
1UPubnubChat* Chat = PubnubChatSubsystem->InitChat("pub-key", "sub-key", "Player_001");
2
3UPubnubChat* Chat = PubnubChatSubsystem->GetChat();
4
5PubnubChatSubsystem->DestroyChat();
1FPubnubChatInitChatResult InitResult = PubnubChatSubsystem->InitChat("pub-key", "sub-key", "Player_001");
2if (InitResult.Result.Error) { return; }
3UPubnubChat* Chat = InitResult.Chat;
4
5UPubnubChat* Chat = PubnubChatSubsystem->GetChat("Player_001");
6
7PubnubChatSubsystem->DestroyChat("Player_001");
8// Or destroy all instances at once:
9PubnubChatSubsystem->DestroyAllChats();
Synced and mutable objects
In 0.x.x, entity objects (channels, users, memberships, messages) were independent snapshots. Two variables that pointed to the same entity ID could hold different data at the same time. Calling Update() on one returned a new object with the updated data, leaving any other variable unchanged.
In 1.0.0, every entity object is synchronized by ID. All variables that hold a reference to the same entity ID share the latest locally known data. Calling Update() on any of them updates the underlying shared state, so every other reference reflects the change immediately.
- 0.x.x
- 1.0.0
1// Channel and Channel2 are independent snapshots
2UPubnubChannel* Channel = Chat->CreatePublicConversation(ChannelID, ChannelData1);
3UPubnubChannel* Channel2 = Chat->GetChannel(ChannelID);
4
5Channel2 = Channel2->Update(ChannelData2);
6// Channel still holds ChannelData1; Channel2 holds ChannelData2
1// Channel and Channel2 both reference the same synchronized object
2UPubnubChatChannel* Channel = Chat->CreatePublicConversation(ChannelID, ChannelData1).Channel;
3UPubnubChatChannel* Channel2 = Chat->GetChannel(ChannelID).Channel;
4
5Channel2->Update(ChannelData2);
6// Both Channel and Channel2 now reflect ChannelData2
This applies to all entity types: channels, users, memberships, and messages. This is the default in 1.0.0. However, review any code that intentionally holds multiple pointers to the same entity ID and relies on them being independent. That pattern no longer works.
Entity-first delegates
Real-time listeners no longer accept a callback parameter. Bind to the entity's delegate property before calling the streaming method. Call the matching StopStreaming*() method to unsubscribe.
- 0.x.x
- 1.0.0
1FOnPubnubMessageStreamUpdateReceived Callback;
2Callback.BindDynamic(this, &AMyActor::OnMessageUpdated);
3UPubnubCallbackStop* Stop = Message->StreamUpdates(Callback);
4
5// Stop:
6Stop->Stop();
1// Bind the delegate first, then start streaming.
2Message->OnUpdatedNative.AddUObject(this, &AMyActor::OnMessageUpdated);
3Message->StreamUpdates();
4
5// Stop:
6Message->StopStreamingUpdates();
7
8// Callback signature:
9void AMyActor::OnMessageUpdated(FString Timetoken, const FPubnubChatMessageData& MessageData) { }
The same pattern applies to all streaming methods across all entities (Channel->StreamPresence(), Channel->StreamTyping(), User->StreamMentions(), Membership->StreamUpdates(), etc.).
Read receipts
The read receipts API was reworked to scale beyond 100 channel members and to separate snapshot fetching from real-time streaming.
In 0.x.x, StreamReadReceipts() returned a list of UserIDs representing members who had read the message. This worked only for channels with 100 or fewer members.
In 1.0.0:
FetchReadReceipts()returns a paginated snapshot of the last-read timetoken for each member. Use this for initial load or on-demand checks.StreamReadReceipts()delivers real-time updates per user via the channel'sOnReadReceiptReceiveddelegate. Each update contains a single user's read position, not a full member list.
- 0.x.x
- 1.0.0
1// Returns TArray<FString> UserIDs — limited to 100 members
2UPubnubCallbackStop* Stop = Channel->StreamReadReceipts(OnReadReceiptsDelegate);
1// One-time paginated snapshot with timetokens
2FPubnubChatFetchReadReceiptsResult FetchResult = Channel->FetchReadReceipts();
3
4// Real-time per-user updates (no member limit)
5Channel->OnReadReceiptReceivedNative.AddUObject(this, &AMyActor::OnReadReceipt);
6Channel->StreamReadReceipts();
7
8// Stop:
9Channel->StopStreamingReadReceipts();
Delete message
The separate soft-delete and hard-delete methods are unified into a single Delete(bool Soft) method.
- 0.x.x
- 1.0.0
1// Soft delete
2UPubnubMessage* SoftDeleted = Message->DeleteMessage();
3
4// Hard delete
5bool bSuccess = Message->DeleteMessageHard();
1// Soft delete (marks as deleted; data remains retrievable)
2FPubnubChatOperationResult Result = Message->Delete(/*Soft=*/ true);
3
4// Hard delete (permanently removes from Message Persistence)
5FPubnubChatOperationResult Result = Message->Delete(/*Soft=*/ false);
Message accessors
Several Message accessors were renamed for consistency with the Get* / Is* / Has* naming convention.
| Old | New |
|---|---|
Message->Text() | Message->GetCurrentText() |
Message->GetTimetoken() | Message->GetMessageTimetoken() |
Message->Deleted() → bool | Message->IsDeleted() → FPubnubChatIsDeletedResult |
Message->Type() → EPubnubChatMessageType | Message->GetType() → FString |
Message->Reactions() → TArray<FPubnubMessageAction> | Message->GetReactions() → FPubnubChatGetReactionsResult |
Message->HasThread() → bool | Message->HasThread() → FPubnubChatHasThreadResult |
- 0.x.x
- 1.0.0
1FString Text = Message->Text();
2FString Timetoken = Message->GetTimetoken();
3bool bDeleted = Message->Deleted();
1FString Text = Message->GetCurrentText();
2FString Timetoken = Message->GetMessageTimetoken();
3FPubnubChatIsDeletedResult DeletedResult = Message->IsDeleted();
4bool bDeleted = DeletedResult.IsDeleted;
Forward message
Forward now takes a UPubnubChatChannel* object instead of a string channel ID.
- 0.x.x
- 1.0.0
1Message->Forward(TEXT("target-channel-id"));
1FPubnubChatChannelResult ChannelResult = Chat->GetChannel(TEXT("target-channel-id"));
2if (!ChannelResult.Result.Error)
3{
4 Message->Forward(ChannelResult.Channel);
5}
GetUsers and GetChannels parameter order
The parameter order for GetUsers() and GetChannels() changed. Limit is now the first parameter. The Sort parameter type also changed from FString to a typed struct.
- 0.x.x
- 1.0.0
1// GetUsers(Filter, Sort, Limit, Page)
2Chat->GetUsers(TEXT(""), TEXT("name:asc"), 25, FPubnubPage());
3
4// GetChannels(Filter, Sort, Limit, Page)
5Chat->GetChannels(TEXT(""), TEXT("id:asc"), 25, FPubnubPage());
1// GetUsers(Limit, Filter, Sort, Page)
2FPubnubGetAllSort SortOptions;
3Chat->GetUsers(25, TEXT(""), SortOptions, FPubnubPage());
4
5// GetChannels(Limit, Filter, Sort, Page)
6Chat->GetChannels(25, TEXT(""), SortOptions, FPubnubPage());
SetRestrictions on Chat
Chat->SetRestrictions() now accepts a single FPubnubChatRestriction struct that includes UserID and ChannelID directly, replacing the three separate parameters.
- 0.x.x
- 1.0.0
1FPubnubRestriction Restriction;
2Restriction.Ban = true;
3Restriction.Reason = TEXT("Spam");
4Chat->SetRestrictions(TEXT("user_123"), TEXT("support"), Restriction);
1FPubnubChatRestriction Restriction;
2Restriction.UserID = TEXT("user_123");
3Restriction.ChannelID = TEXT("support");
4Restriction.Ban = true;
5Restriction.Reason = TEXT("Spam");
6Chat->SetRestrictions(Restriction);
MessageDraft
MessageDraft was rewritten as a native Unreal implementation. Key changes:
- Change listeners replaced by delegate properties.
- Message elements are now plain structs instead of UObjects.
- New
AppendText()method for easily appending plain text to the end of a draft. - Draft mutations are now validated internally — the SDK prevents operations that would corrupt existing mentions or links (for example, inserting text in the middle of a mention offset range).
- 0.x.x
- 1.0.0
1// Adding a change listener
2Draft->AddChangeListener(Callback);
3
4// Adding a mention target (UObject)
5UPubnubMentionTarget* Target = UPubnubMentionTarget::CreateUserMentionTarget(TEXT("user-id"));
6Draft->AddMention(5, 4, Target);
7
8// Reading elements (UObjects)
9TArray<UPubnubMessageElement*> Elements = /* delivered via callback */;
10FString Text = Elements[0]->GetText();
1// Bind delegate property instead of AddChangeListener
2Draft->OnMessageDraftUpdatedNative.AddUObject(this, &AMyActor::OnDraftUpdated);
3
4// Append plain text to the draft
5Draft->AppendText(TEXT(" — see you there!"));
6
7// Mention target is now a struct created via a utilities class
8FPubnubChatMentionTarget Target = UPubnubChatMessageDraftUtilities::CreateUserMentionTarget(TEXT("user-id"));
9Draft->AddMention(5, 4, Target);
10
11// Elements are now value-type structs
12TArray<FPubnubChatMessageElement> Elements = Draft->GetMessageElements();
13FString Text = Elements[0].Text;
Sample widget
Unreal Chat SDK 1.0.0 includes a sample UI widget (W_PubnubChatSample) in the plugin's Content folder. The widget demonstrates initialization, sending and receiving messages, presence, and moderation. Open it in the Unreal Editor to see a working reference implementation before building your own UI.
New capabilities in 1.0.0
The following features are new in 1.0.0 and have no counterpart in 0.x.x. Consider adopting them after completing your migration.
| Feature | Description |
|---|---|
| Connection status | Chat->OnConnectionStatusChanged delegate fires with EPubnubChatConnectionStatus (PCCS_ConnectionOnline, PCCS_ConnectionOffline, PCCS_ConnectionError) whenever the underlying PubNub connection changes. Refer to connection management for more details. |
| Disconnect / Reconnect | Chat->DisconnectSubscriptions() drops all active subscriptions; Chat->ReconnectSubscriptions(FString Timetoken) restores them, optionally catching up from a timetoken. |
| Rate limiter | FPubnubChatRateLimiterConfig in FPubnubChatConfig lets you configure per-channel-type send rate limits and an exponential backoff factor. See Config → RateLimiter in Initial configuration. |
EmitReadReceiptEvents config | FPubnubChatConfig.EmitReadReceiptEvents (TMap<FString, bool>) controls which channel types emit read receipt signals (public, group, direct). |
| Global presence | StoreUserActivityTimestamps and StoreUserActivityInterval in FPubnubChatConfig track the last active time for all users. See global presence. |
InitChatAsync() | Non-blocking initialization via PubnubChatSubsystem->InitChatAsync(PubKey, SubKey, UserID, OnInitChatDelegate). |
| Native lambda callbacks | All *Async methods accept a *Native delegate variant that takes a lambda instead of a UObject delegate. For example, FOnPubnubChatOperationResponseNative. |
Migration steps
To migrate from Unreal Chat SDK 0.x.x to 1.0.0:
- Download the PubNub Unreal Engine Core SDK plugin and place it in your project's
Pluginsfolder. Delete theIntermediateandBinariesfolders and rebuild the project. - Update every
Join()call that expects to receive messages: add aConnect()call afterJoin()and bind the channel's message delegate before callingConnect(). - Rename all entity types using the type renames table. Rename
CustomDataJsontoCustomon data structs. - Update enum value names to the new PascalCase format.
- Update
InitChat,GetChat, andDestroyChatcalls to pass aUserIDargument. Extract theChatpointer fromFPubnubChatInitChatResult. - Update all method return-value handling to check
Result.Errorbefore using the payload. - Migrate streaming calls to the entity-first delegate pattern: bind to the entity's delegate property, call the parameterless
Stream*()method, and callStopStreaming*()when done. - Update read receipts usage: replace
StreamReadReceipts()list-based calls withFetchReadReceipts()for snapshots and bindOnReadReceiptReceivedfor real-time updates. - Replace
DeleteMessage()andDeleteMessageHard()withDelete(bool Soft). - Rename message accessors:
Text()→GetCurrentText(),GetTimetoken()→GetMessageTimetoken(),Deleted()→IsDeleted(),Type()→GetType(),Reactions()→GetReactions(). - Update
Forward()calls to pass aUPubnubChatChannel*object instead of a string ID. - Swap
GetUsers()andGetChannels()parameter order to(Limit, Filter, Sort, Page)and update theSortargument type toFPubnubGetAllSort. - Update
Chat->SetRestrictions()to use the unifiedFPubnubChatRestrictionstruct. - Migrate
MessageDraftchange listeners toOnMessageDraftUpdateddelegate properties and updateMentionTargetandMessageElementusage to the new struct types. - Consider adopting
*Asyncmethod variants to avoid blocking the game thread where appropriate. - Review any code that holds multiple pointers to the same entity ID and relies on them being independent snapshots. In
1.0.0, all references to the same entity ID share the latest locally known data, so patterns that depended on independent copies will behave differently.
If you encounter issues during migration, contact PubNub Support.