Unity Chat SDK 2.0.0 migration guide
The 2.0.0 release of the Unity Chat SDK introduces a set of API improvements that make the SDK more consistent, more expressive, and better suited for large-scale chat applications. The changes include a revised channel join/connect lifecycle, entity-specific event names that are easier to discover, structured types for event payloads that previously required manual parsing, and a reworked read-receipt and reaction model.
This guide is for developers who use Unity Chat SDK 1.x.x in existing applications.
If your application uses 1.x.x, it will not compile against 2.0.0 without changes. This guide summarizes the differences between the versions and shows how to migrate to Unity Chat SDK 2.0.0.
Most notable changes include:
JoinChannelandConnectreplaceJoin: Joining a channel no longer starts message delivery automatically. CallJoinChannel()to create the membership andConnect()separately to start receiving messages. A matchingLeaveChannel()andDisconnect()replaceLeave().- Renamed events on entities: Events on
Channel,User,Membership, andMessageobjects were renamed to shorter, entity-agnostic names. For example,OnChannelUpdateis nowOnUpdatedandOnUserUpdatedis nowOnUpdated. Deletion events are now separate (OnDeleted), and theChatEntityChangeTypeparameter was removed from allStreamUpdatesOncallbacks. - Typed event payloads: Events that previously delivered raw
ChatEventobjects now deliver purpose-built types.OnMessageReporteddeliversMessageReport,OnMentioneddeliversMention,OnInviteddeliversInvite, andOnRestrictionChangeddeliversChannelRestriction. - New read receipt format:
OnReadReceiptEventnow fires once per individual user receipt rather than aggregating all memberships into a dictionary. A newReadReceiptstruct carriesUserIdandLastReadTimeToken. AGetReadReceipts()method provides the full snapshot when needed. - New message reaction format: A new
MessageReactions()method returns reactions grouped by value with aCountand anIsMineflag, replacing the need to iterate rawMessageActionlists. SendTextParamssimplified:MentionedUsers,QuotedMessage, andFileswere removed fromSendTextParamsand must now be set throughMessageDraft.- Soft deletion deprecated: The
bool softparameter on delete methods for Users and Channels is deprecated. Hard delete is now the default behavior.
Differences between 1.x.x and 2.0.0
| Feature/Method | 1.x.x | 2.0.0 |
|---|---|---|
| Joining a channel | Join() implicitly calls Connect() | JoinChannel() and Connect() are separate calls |
| Leaving a channel | Leave() | LeaveChannel() and Disconnect() |
| Channel update event | OnChannelUpdate | OnUpdated |
| Channel/user/membership deletion event | Part of the OnUpdate callback with ChatEntityChangeType | Separate OnDeleted event |
| Message update event | OnMessageUpdated | OnUpdated |
| User update event | OnUserUpdated | OnUpdated |
| Membership update event | OnMembershipUpdated | OnUpdated |
StreamUpdatesOn callback signature | Action<T, ChatEntityChangeType> | Action<T> (change type removed) |
Report event on Channel | OnReportEvent with raw ChatEvent payload | OnMessageReported with typed MessageReport |
Mention event on User | OnMentionEvent with raw ChatEvent payload | OnMentioned with typed Mention |
Invite event on User | OnInviteEvent with raw ChatEvent payload | OnInvited with typed Invite |
Moderation event on User | OnModerationEvent with raw ChatEvent payload | OnRestrictionChanged with typed ChannelRestriction |
| Read receipt event payload | Dictionary<string, List<string>> (timetoken → user IDs) | ReadReceipt { UserId, LastReadTimeToken } |
| Read receipt snapshot | No dedicated method | Channel.GetReadReceipts() |
| Message reactions | message.Reactions → raw List<MessageAction> | message.MessageReactions() → List<MessageReaction> with Count and IsMine |
Mentioning, quoting, and files in SendText | Set on SendTextParams | Set on MessageDraft before calling draft.Send() |
| Custom events | chat.EmitEvent(...) (public) | chat.EmitEvent(...) is now internal; use channel.EmitCustomEvent(...) |
| Emitting user mentions | channel.EmitUserMention(...) (public) | channel.EmitUserMention(...) is now protected |
| Soft delete | Delete(bool soft = false) | bool soft parameter deprecated for Users and Channels; delete is hard by default |
| Read receipt emission control | Always emitted | Controlled per channel type via PubnubChatConfig.EmitReadReceiptEvents |
CreateDirectConversation / CreateGroupConversation membership data | Single membershipData parameter | hostMembershipData + inviteeMembershipData parameters |
| Membership deletion | Not available from Membership object | membership.Delete() |
JoinChannel and Connect
Important change
This change affects any code that calls Join() and then listens for messages. Update it before releasing to users.
In 1.x.x, calling Join() on a channel also subscribed to that channel automatically — messages started arriving immediately. In 2.0.0, JoinChannel() creates the membership server-side but does not start message delivery. Call Connect() separately after JoinChannel() to begin receiving messages.
The reverse operation has also changed: use LeaveChannel() and Disconnect() instead of Leave().
- 1.x.x
- 2.0.0
1// Join implicitly started message delivery
2await channel.Join();
3
4// Messages arrived via channel.OnMessageReceived
1// JoinChannel creates the server-side membership
2var joinResult = await channel.JoinChannel();
3if (joinResult.Error) { return; }
4
5// Connect starts message delivery
6channel.Connect();
7
8// Leave:
9channel.Disconnect();
10await channel.LeaveChannel();
Renamed events on Channel
Several Channel events have been renamed and split for clarity.
OnChannelUpdate is renamed to OnUpdated. The previously overloaded OnUpdate callback (which received a ChatEntityChangeType parameter) is removed — hard deletion is now signalled by the new OnDeleted event.
OnReportEvent is renamed to OnMessageReported. The payload changes from a raw ChatEvent to a structured MessageReport.
OnReadReceiptEvent remains, but its payload type changes from Dictionary<string, List<string>> to the new ReadReceipt struct. See Read receipts for full details.
- 1.x.x
- 2.0.0
1channel.OnChannelUpdate += (updatedChannel) =>
2{
3 Debug.Log($"Channel updated: {updatedChannel.Id}");
4};
5
6channel.OnUpdate += (updatedChannel, changeType) =>
7{
8 if (changeType == ChatEntityChangeType.Delete)
9 {
10 Debug.Log("Channel was deleted");
11 }
12};
13
14channel.OnReportEvent += (chatEvent) =>
15{
show all 17 lines1channel.OnUpdated += (updatedChannel) =>
2{
3 Debug.Log($"Channel updated: {updatedChannel.Id}");
4};
5
6channel.OnDeleted += () =>
7{
8 Debug.Log("Channel was deleted");
9};
10
11channel.OnMessageReported += (report) =>
12{
13 Debug.Log($"Message reported — reason: {report.Reason}, by user: {report.ReportedUserId}");
14};
Renamed events on User
OnUserUpdated is renamed to OnUpdated. Deletion is now signalled by the separate OnDeleted event rather than through the overloaded OnUpdate callback.
Events that previously delivered raw ChatEvent objects now carry purpose-built types:
| Old event | New event | New payload type |
|---|---|---|
OnMentionEvent | OnMentioned | Mention |
OnInviteEvent | OnInvited | Invite |
OnModerationEvent | OnRestrictionChanged | ChannelRestriction |
- 1.x.x
- 2.0.0
1user.OnUserUpdated += (updatedUser) => { Debug.Log(updatedUser.Id); };
2
3user.OnMentionEvent += (chatEvent) =>
4{
5 Debug.Log($"Mentioned in: {chatEvent.ChannelId}");
6};
7
8user.OnInviteEvent += (chatEvent) =>
9{
10 Debug.Log($"Invited to: {chatEvent.ChannelId}");
11};
12
13user.OnModerationEvent += (chatEvent) =>
14{
15 Debug.Log($"Moderation event: {chatEvent.Payload}");
show all 16 lines1user.OnUpdated += (updatedUser) => { Debug.Log(updatedUser.Id); };
2
3user.OnDeleted += () => { Debug.Log("User was deleted"); };
4
5user.OnMentioned += (mention) =>
6{
7 Debug.Log($"Mentioned in: {mention.ChannelId}, text: {mention.Text}");
8};
9
10user.OnInvited += (invite) =>
11{
12 Debug.Log($"Invited to: {invite.ChannelId} by: {invite.InvitedByUserId}");
13};
14
15user.OnRestrictionChanged += (restriction) =>
show all 18 linesRenamed events on Membership and Message
OnMembershipUpdated on Membership is renamed to OnUpdated. OnMessageUpdated on Message is renamed to OnUpdated. Both entities gain a separate OnDeleted event for hard deletion.
1// Before
2membership.OnMembershipUpdated += (m) => { };
3message.OnMessageUpdated += (m) => { };
4
5// After
6membership.OnUpdated += (m) => { };
7membership.OnDeleted += () => { };
8
9message.OnUpdated += (m) => { };
StreamUpdatesOn callback signatures
The ChatEntityChangeType parameter has been removed from all StreamUpdatesOn callback signatures. Update callbacks on Channel, User, Membership, and Message.
- 1.x.x
- 2.0.0
1Channel.StreamUpdatesOn(channels, (channel, changeType) =>
2{
3 Debug.Log($"Channel changed: {changeType}");
4});
5
6Membership.StreamUpdatesOn(memberships, (membership, changeType) =>
7{
8 Debug.Log($"Membership changed: {changeType}");
9});
1Channel.StreamUpdatesOn(channels, (channel) =>
2{
3 Debug.Log($"Channel updated: {channel.Id}");
4});
5
6Membership.StreamUpdatesOn(memberships, (membership) =>
7{
8 Debug.Log($"Membership updated for: {membership.Channel.Id}");
9});
The same change applies to User.StreamUpdatesOn and Message.StreamUpdatesOn.
Read receipts
Important change
This change affects any code that reads OnReadReceiptEvent payload. Update the handler before releasing to users.
In 1.x.x, OnReadReceiptEvent fired with a Dictionary<string, List<string>> where keys were timetokens and values were lists of user IDs who had read up to that point. Computing this required a call to GetChannelMemberships on every receipt event.
In 2.0.0:
OnReadReceiptEventfires once per incoming receipt signal, delivering a singleReadReceiptvalue that contains the user ID and their last-read timetoken. No membership fetch is required.Channel.GetReadReceipts()provides an on-demand snapshot of all current read receipts asList<ReadReceipt>.
- 1.x.x
- 2.0.0
1channel.OnReadReceiptEvent += (Dictionary<string, List<string>> receipts) =>
2{
3 foreach (var kvp in receipts)
4 {
5 string timetoken = kvp.Key;
6 List<string> userIds = kvp.Value;
7 Debug.Log($"Timetoken {timetoken} read by {userIds.Count} users");
8 }
9};
1// Real-time updates — one event per user receipt
2channel.OnReadReceiptEvent += (ReadReceipt receipt) =>
3{
4 Debug.Log($"User {receipt.UserId} read up to {receipt.LastReadTimeToken}");
5};
6
7// On-demand snapshot of all current receipts
8var result = await channel.GetReadReceipts();
9foreach (ReadReceipt receipt in result.Result)
10{
11 Debug.Log($"{receipt.UserId}: {receipt.LastReadTimeToken}");
12}
Controlling which channel types emit read receipts
A new EmitReadReceiptEvents property in PubnubChatConfig controls which channel types produce read receipt signals. The default setting disables read receipts on public channels, which is appropriate for most large-scale deployments.
1// Default: public = false, group = true, direct = true
2// To restore old behavior (always emit), set all to true:
3var config = new PubnubChatConfig(publishKey, subscribeKey, userId,
4 emitReadReceiptEvents: new Dictionary<string, bool>
5 {
6 { "public", true },
7 { "group", true },
8 { "direct", true }
9 });
Message reactions
In 1.x.x, the raw message.Reactions property returned a flat List<MessageAction>. Grouping reactions by type, counting them, or checking whether the current user had reacted required manual processing.
In 2.0.0, the new message.MessageReactions() method returns List<MessageReaction>. Each MessageReaction aggregates reactions of the same value and exposes Count (how many users reacted) and IsMine (whether the current user is among them). The raw message.Reactions property remains available for backwards-compatible reading.
- 1.x.x
- 2.0.0
1// Raw list — manual grouping required
2var reactions = message.Reactions;
3var grouped = reactions
4 .GroupBy(r => r.Value)
5 .Select(g => new { Value = g.Key, Count = g.Count() });
6
7foreach (var group in grouped)
8{
9 Debug.Log($"{group.Value}: {group.Count}");
10}
1// Aggregated by reaction value — count and IsMine included
2var reactions = message.MessageReactions();
3foreach (MessageReaction reaction in reactions)
4{
5 Debug.Log($"{reaction.Value}: {reaction.Count} reaction(s), mine: {reaction.IsMine}");
6}
SendTextParams and MessageDraft
MentionedUsers, QuotedMessage, and Files have been removed from SendTextParams and are now properties on MessageDraft. To send a message with mentions, a quoted message, or attachments, create a draft, configure it, and send.
- 1.x.x
- 2.0.0
1await channel.SendText("See you there!", new SendTextParams
2{
3 QuotedMessage = someMessage,
4 MentionedUsers = new Dictionary<int, User> { { 8, mentionedUser } },
5 Files = new List<InputFile> { myFile }
6});
1var draft = channel.CreateMessageDraft();
2draft.InsertText(0, "See you there!");
3draft.QuotedMessage = someMessage;
4draft.AddMention(8, mentionedUser.Id.Length, MentionTarget.User(mentionedUser.Id));
5draft.Files = new List<InputFile> { myFile };
6await draft.Send();
A new AppendText() convenience method is also available:
1draft.AppendText(" — see you there!");
Custom events
chat.EmitEvent() is now internal and can no longer be called from application code. Use channel.EmitCustomEvent() instead, passing the channel you want to emit to directly.
- 1.x.x
- 2.0.0
1await chat.EmitEvent(PubnubChatEventType.Custom, "my-channel", "{\"key\":\"value\"}");
1await channel.EmitCustomEvent("{\"key\":\"value\"}", storeInHistory: true);
CreateDirectConversation and CreateGroupConversation
Both conversation-creation methods now accept separate membership data for the host and for the invited user(s).
- 1.x.x
- 2.0.0
1await chat.CreateDirectConversation(user, channelId, channelData,
2 membershipData: hostMembershipData);
3
4await chat.CreateGroupConversation(users, channelId, channelData,
5 membershipData: hostMembershipData);
1await chat.CreateDirectConversation(user, channelId, channelData,
2 hostMembershipData: hostData,
3 inviteeMembershipData: inviteeData);
4
5await chat.CreateGroupConversation(users, channelId, channelData,
6 hostMembershipData: hostData,
7 inviteesMembershipData: inviteesData);
Soft deletion deprecated
The bool soft parameter on delete methods is deprecated. If you rely on soft deletion (marking entities as deleted while retaining data), migrate to explicit hard deletion using the new parameter-free Delete() methods. If you need to preserve the soft-delete behavior during a transitional period, the old overloads still compile, but they will be removed in a future version.
1// Deprecated — soft parameter will be removed
2await user.DeleteUser(soft: true);
3await channel.Delete(soft: false);
4
5// Preferred in 2.0.0
6await user.Delete();
7await channel.Delete();
8await membership.Delete(); // new: Delete() is now available directly on Membership
New capabilities in 2.0.0
The following features are new in 2.0.0 and have no counterpart in 1.x.x.
| Feature | Description |
|---|---|
GetReadReceipts() | await channel.GetReadReceipts() returns a paginated List<ReadReceipt> snapshot of the current read state for all channel members. |
MessageReactions() | message.MessageReactions() returns reactions grouped by value with Count and IsMine fields. |
HasMember() / GetMember() | await channel.HasMember(userId) and await channel.GetMember(userId) let you check and retrieve a specific member without fetching the full membership list. |
IsMemberOn() / GetMembership() | await user.IsMemberOn(channelId) and await user.GetMembership(channelId) let you check and retrieve a specific membership from the User object. |
Membership.Delete() | await membership.Delete() deletes a membership directly from the Membership object. |
ReconnectSubscriptions() / DisconnectSubscriptions() | chat.ReconnectSubscriptions() restores all active subscriptions; chat.DisconnectSubscriptions() drops them cleanly. |
OnSubscriptionStatusChanged | chat.OnSubscriptionStatusChanged fires with a PNStatus value whenever the underlying PubNub connection changes. Enable it with chat.StreamSubscriptionStatus(true). |
ChatUtils (public) | ChatUtils.TimeTokenNow() and ChatUtils.TimeToken(DateTime date) are now publicly accessible. |
MessageDraft.AppendText() | Appends a plain-text string to the end of a draft without needing to compute the current cursor position. |
MessageDraft.GetMessageElements() | MessageDraft.GetMessageElements(text) (static) and message.GetMessageElements() parse a message text into a structured List<MessageElement> for rendering mentions and links. |
EmitReadReceiptEvents config | PubnubChatConfig.EmitReadReceiptEvents (Dictionary<string, bool>) controls which channel types emit read receipt signals, letting you reduce unnecessary network traffic on public channels. |
Migration steps
To migrate from Unity Chat SDK 1.x.x to 2.0.0:
- Replace every
channel.Join()call withawait channel.JoinChannel()followed bychannel.Connect(). Replace everychannel.Leave()call withchannel.Disconnect()followed byawait channel.LeaveChannel(). - Rename
OnChannelUpdatetoOnUpdatedonChannel. Remove anyOnUpdatehandlers that checked forChatEntityChangeType.Deleteand replace them withOnDeletedhandlers. Apply the same pattern toUser,Membership, andMessage. - Rename
OnMessageUpdatedonMessagetoOnUpdated. - Rename
OnUserUpdatedonUsertoOnUpdated. - Rename
OnMembershipUpdatedonMembershiptoOnUpdated. - Update
StreamUpdatesOncallbacks onChannel,User,Membership, andMessageto remove theChatEntityChangeTypeparameter. - Rename
OnMentionEventtoOnMentionedonUserand update the handler to use theMentiontype. RenameOnInviteEventtoOnInvited(useInvitetype). RenameOnModerationEventtoOnRestrictionChanged(useChannelRestrictiontype). - Rename
OnReportEventtoOnMessageReportedonChanneland update the handler to use theMessageReporttype. - Update all
OnReadReceiptEventhandlers to use the newReadReceiptstruct (UserId,LastReadTimeToken) instead of the old dictionary type. Replace any manual membership-aggregation logic withGetReadReceipts()where a full snapshot is needed. - Replace raw
message.Reactionsiteration withmessage.MessageReactions()where reaction counts orIsMinechecks are needed. - Move
QuotedMessage,MentionedUsers, andFilesout ofSendTextParamsand onto aMessageDraftinstance. Calldraft.Send()instead ofchannel.SendText()for messages that use these features. - Replace
chat.EmitEvent(...)calls withchannel.EmitCustomEvent(...). - Update
CreateDirectConversationandCreateGroupConversationcalls to use the renamedhostMembershipDataand the newinviteeMembershipData/inviteesMembershipDataparameters. - Remove the
bool softparameter from anyDeleteUser(),Channel.Delete(), orChat.DeleteChannel()calls. Migrate to the new parameter-freeDelete()methods. - Review
PubnubChatConfiginitialization and addEmitReadReceiptEventsif you need non-default read receipt emission behavior.
If you encounter issues during migration, contact PubNub Support.