On this page

JavaScript Chat SDK 1.0.0 migration guide

The 1.0.0 release of the JavaScript Chat SDK introduces a set of API improvements that make real-time subscriptions more discoverable, event payloads strongly typed, and membership management more expressive. The core change is the shift from generic listener methods to entity-owned callbacks: instead of calling chat.listenForEvents() or passing a callback to channel.connect(), you now attach callbacks directly to entity objects (channel.onMessageReceived, user.onMentioned, etc.).

This guide is for developers who use JavaScript 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 to join(), Message.reactions, and GetCurrentUserMentionsResult. This guide summarizes the differences between the versions and shows how to migrate to JavaScript Chat SDK 1.0.0.

Most notable changes include:

  • join() signature change: The callback parameter is removed and join() no longer subscribes to messages automatically. Call onMessageReceived() separately after joining to start message delivery. join() now returns Promise<Membership> directly instead of a JoinResult wrapper.
  • Entity-first callbacks: All streaming methods gain entity-owned alternatives. connect()onMessageReceived(), getTyping()onTypingChanged(), streamPresence()onPresenceChanged(), streamUpdates()onUpdated() + onDeleted(). The old methods are deprecated but still compile.
  • Typed event payloads: Events that previously required manual EventContent parsing now deliver purpose-built types. onMentioned() delivers Mention, onInvited() delivers Invite, onRestrictionChanged() delivers a restriction object, and onReadReceiptReceived() delivers ReadReceipt.
  • Message.reactions type change: Message.reactions changed from an object of reaction lists to MessageReaction[]. Each MessageReaction exposes value, isMine, and userIds.
  • GetCurrentUserMentionsResult.mentions: The enhancedMentionsData field is deprecated (still available) and a new mentions field is added. Its element type changes to a unified UserMention type with fields message (a Message object), userId, channelId, and parentChannelId?.
  • sendText() renamed: The old multi-parameter sendText() overload is renamed to sendTextLegacy(). Use the new sendText(text, options?: SendTextParams) form.
  • MessageDraft is now the primary draft class: What was MessageDraftV2 is now MessageDraft. The old MessageDraft class is renamed to MessageDraftV1 and deprecated. Use channel.createMessageDraft() — it now returns the new MessageDraft.
  • New read receipt capabilities: channel.fetchReadReceipts() returns a paginated snapshot of read positions. A new emitReadReceiptEvents configuration option controls which channel types emit read receipt signals.
  • New membership methods: channel.hasMember(), channel.getMember(), user.isMemberOf(), and user.getMembership() let you check and retrieve a specific membership without fetching the full list.

Differences between 0.x.x and 1.0.0

Feature/Method0.x.x1.0.0
join() return type
Promise<JoinResult> (includes disconnect)
Promise<Membership>
join() signature
join(custom?, callback?)
join(params?: { status?, type?, custom? })
JoinResult
Available
Removed
Message delivery after join()
Automatic (via callback)
Requires explicit onMessageReceived() call
Disconnect from message delivery
joinResult.disconnect() (function)
disconnect() returned by onMessageReceived()
connect()
Primary method for message delivery
Deprecated — use channel.onMessageReceived()
getTyping()
Typing indicator streaming
Deprecated — use channel.onTypingChanged()
streamPresence()
Presence streaming
Deprecated — use channel.onPresenceChanged()
streamUpdates() on entity
Update streaming (single entity)
Deprecated — use onUpdated()
streamUpdatesOn() on entity
Update streaming (multiple entities)
Still supported
Deletion events
Part of streamUpdates() callback
Separate onDeleted() event
streamReadReceipts()
{ [timetoken: string]: string[] } callback
Deprecated — use channel.onReadReceiptReceived() with typed ReadReceipt
streamMessageReports()
Raw event callback
Deprecated — use channel.onMessageReported()
Message.reactions type
Object of reaction lists
MessageReaction[]
Message.actions
Available
Deprecated
Mention events
chat.listenForEvents({ type: "mention" })
user.onMentioned(Mention)
Invite events
chat.listenForEvents({ type: "invite" })
user.onInvited(Invite)
Moderation events
chat.listenForEvents({ type: "moderation" })
user.onRestrictionChanged({ userId, channelId, ban, mute, reason? })
Custom events (emit)
chat.emitEvent()
Deprecated — use channel.emitCustomEvent()
Custom events (listen)
chat.listenForEvents({ type: "custom" })
Deprecated — use channel.onCustomEvent()
EventContent.* types
Available
Deprecated
Old sendText() overload
sendText(text, options) with legacy params
Renamed to sendTextLegacy()
New sendText() overload
Not available
sendText(text, options?: SendTextParams)
Primary draft class
MessageDraftV2 (via createMessageDraftV2())
MessageDraft (via createMessageDraft())
Old draft class
MessageDraft (via createMessageDraft())
MessageDraftV1 (via createMessageDraftV1()), deprecated
Read receipts snapshot
Not available
channel.fetchReadReceipts()ReadReceiptsResponse
Read receipt timetokens
N/A
string (not number)
emitReadReceiptEvents config
Not available
Available in ChatConfig
GetCurrentUserMentionsResult field
enhancedMentionsData
mentions: UserMention[]
Membership existence check
getMembers() then filter
channel.hasMember(userId), channel.getMember(userId)
User membership check
getMemberships() then filter
user.isMemberOf(channelId), user.getMembership(channelId)
removeThread() return
Composite object
Promise<boolean>
createThread()
No arguments, returns ThreadChannel
Required text parameter, returns CreateThreadResult
Channel.delete() / User.delete()
Accepts soft parameter
soft parameter removed; returns Promise<true>
Membership.update() signature
update({ custom })
update({ status?, type?, custom? })

join() and onMessageReceived()

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 both created the membership server-side and subscribed to message delivery automatically — messages arrived via the callback you passed to join(), and the returned disconnect function stopped the subscription. In 1.0.0, join() creates the membership only. Call onMessageReceived() separately to start receiving messages.

The JoinResult type is removed. join() now returns Promise<Membership> directly. The disconnect function is returned by onMessageReceived().

1// join() both created the membership and started message delivery
2const { membership, disconnect } = await channel.join(
3 (message) => {
4 console.log("New message:", message.text);
5 },
6 { role: "member" }
7);
8
9// Stop message delivery:
10disconnect();
11await channel.leave();

Entity-first callbacks

In 0.x.x, streaming subscriptions were created by calling methods on the Chat or Channel object and passing a callback as a parameter. In 1.0.0, entity objects expose dedicated callback methods. Pass a handler function and receive a disconnect function in return — call it to stop the subscription.

The old instance methods (connect(), getTyping(), streamPresence(), streamUpdates()) are deprecated but still compile. The static streamUpdatesOn() helper remains supported. Migrate to the new names at your own pace.

1// Channel — message delivery
2const stopMessages = channel.connect((message) => {
3 console.log("Message:", message.text);
4});
5
6// Channel — typing indicator
7const stopTyping = channel.getTyping((typingUserIds) => {
8 console.log("Typing:", typingUserIds);
9});
10
11// Channel — presence
12const stopPresence = channel.streamPresence((presence) => {
13 console.log("Present:", presence.occupants);
14});
15
show all 29 lines

The same pattern applies to message.onUpdated() for individual message update subscriptions.

Typed event payloads

In 0.x.x, invite, mention, and moderation events required calling chat.listenForEvents() with a type string and reading fields from the raw event payload. In 1.0.0, these events are exposed as dedicated entity callbacks with purpose-built types. The chat.listenForEvents() method and all EventContent types are deprecated.

1// Mention events — raw event with untyped payload
2const stopMentions = chat.listenForEvents({
3 channel: currentUser.id,
4 type: "mention",
5 callback: (event) => {
6 console.log("Mentioned in:", event.payload.channelId);
7 },
8});
9
10// Invite events
11const stopInvites = chat.listenForEvents({
12 channel: currentUser.id,
13 type: "invite",
14 callback: (event) => {
15 console.log("Invited to:", event.payload.channelId);
show all 31 lines

Read receipts

The read receipts API gains two new capabilities in 1.0.0: a typed real-time callback (onReadReceiptReceived()) and an on-demand snapshot (fetchReadReceipts()). The old streamReadReceipts() method is deprecated.

In 0.x.x, streamReadReceipts() delivered an object on every event, where keys were timetokens and values were arrays of user IDs who had read up to that point.

In 1.0.0, onReadReceiptReceived() fires once per incoming receipt signal and delivers a single ReadReceipt value containing the user ID and their last-read timetoken as a string. fetchReadReceipts() provides a paginated snapshot of the current read state for all channel members.

note

Read receipt timetokens are string values in the JavaScript SDK, unlike the Long type in Kotlin.

1const stop = channel.streamReadReceipts((receipts) => {
2 // receipts: { [timetoken: string]: string[] } — timetoken to user IDs
3 Object.entries(receipts).forEach(([timetoken, userIds]) => {
4 console.log(`Timetoken ${timetoken} read by`, userIds);
5 });
6});

Controlling which channel types emit read receipts

A new emitReadReceiptEvents option in ChatConfig controls which channel types produce read receipt signals. By default, public channels do not emit read receipts.

1const chat = await Chat.init({
2 publishKey: "...",
3 subscribeKey: "...",
4 userId: "...",
5 // Default: public = false, group = true, direct = true
6 // To restore the old behavior (always emit), set all to true:
7 emitReadReceiptEvents: {
8 public: true,
9 group: true,
10 direct: true,
11 },
12});
Behavioral change

In 1.0.0, setLastReadMessage(), setLastReadMessageTimetoken(), and markAllMessagesAsRead() now emit receipt events by default on direct and group channels. If your app previously relied on these methods not emitting events, you may see unexpected receipt signals and additional network traffic after upgrading. Set emitReadReceiptEvents to { direct: false, group: false } to restore the old behavior.

Message reactions

Important change

This change affects any code that reads Message.reactions. Update it before releasing to users.

In 0.x.x, Message.reactions returned a raw object of reaction lists. Grouping reactions by value, counting them, or checking whether the current user had reacted required manual processing.

In 1.0.0, Message.reactions returns MessageReaction[]. Each MessageReaction aggregates reactions of the same value and exposes isMine (whether the current user is among them) and userIds (array of reacting user IDs). The Message.actions property is deprecated.

1// Raw object — manual grouping required
2const rawReactions = message.reactions;
3Object.entries(rawReactions).forEach(([type, actions]) => {
4 console.log(`${type}: ${actions.length} reaction(s)`);
5 const isMine = actions.some((a) => a.uuid === chat.currentUser.id);
6 console.log("Mine:", isMine);
7});

Sending messages in 1.0.0

1.0.0 separates message sending into two distinct paths: a lightweight API for plain text and a composition API for rich content.

Use caseAPI
Send plain text with delivery options (meta, ttl, storeInHistory, and so on)
channel.sendText(text, options?: SendTextParams)
Build messages with mentions, channel references, links, files, or quoted messages
channel.createMessageDraft()draft.send()
Migrate existing code that passed quotedMessage or files to the old sendText()
channel.sendTextLegacy() — migration only, do not use for new code

SendTextParams covers lightweight delivery options only: meta, storeInHistory, sendByPost, ttl, and customPushData. It does not accept rich-content parameters.

For rich composition, channel.createMessageDraft() returns the MessageDraft class (previously MessageDraftV2 in 0.x.x). Use it whenever your UI has a text composer that handles mentions, channel references, links, files, or quoted messages.

Deprecated overload

The previous sendText() overload accepting SendTextOptionParams (which included quotedMessage and files as top-level options) was renamed to sendTextLegacy().

sendTextLegacy() is kept only to ease migration from 0.x.x. Do not use it for new integrations — use sendText() for plain text or createMessageDraft() for rich content.

Simple send

1const channel = await chat.getChannel("support")
2await channel.sendText("Hi everyone!", {
3 meta: { priority: "high" },
4 storeInHistory: true,
5 ttl: 15,
6})

Rich send with MessageDraft

1const channel = await chat.getChannel("support")
2if (!channel) throw new Error("Channel not found")
3
4const draft = channel.createMessageDraft({
5 userSuggestionSource: "channel",
6 isTypingIndicatorTriggered: true,
7 userLimit: 10,
8 channelLimit: 10,
9})
10
11draft.addChangeListener(async (state) => {
12 const elements = state.messageElements
13 const suggestions = await state.suggestedMentions
14 renderComposer(elements)
15 renderSuggestions(suggestions)
show all 27 lines

Migrating from 0.x.x

1await channel.sendText("See you there!", {
2 quotedMessage: someMessage,
3 mentionedUsers: { 8: mentionedUser },
4 files: [myFile],
5});

removeThread() return type

In 0.x.x, removeThread() returned a composite object. In 1.0.0, it returns Promise<boolean>.

1const { threadChannel, parentMessage } = await message.removeThread()
2console.log("Thread removed from:", threadChannel.id)

createThread() now requires text

In 0.x.x, createThread() took no arguments and created an empty thread channel. In 1.0.0, createThread(text, options?) requires the first reply text, sends it as the initial thread message, and returns CreateThreadResult containing both threadChannel and parentMessage.

1const threadChannel = await message.createThread()
2await threadChannel.sendText("First reply")

Soft-delete removed from entity deletion

In 0.x.x, Channel.delete(), ThreadChannel.delete(), User.delete(), Chat.deleteChannel(), and Chat.deleteUser() accepted a soft parameter for soft-deletion. In 1.0.0, the soft parameter is removed — all deletions are permanent. Return types are simplified to Promise<true>.

1await channel.delete({ soft: true })
note

Message soft-delete (Message.delete({ soft: true })) is unaffected by this change.

Membership.update() signature expanded

In 0.x.x, Membership.update() accepted only custom. In 1.0.0, it also accepts status and type.

1await membership.update({ custom: { role: "admin" } })

MessageDraft class rename

What was MessageDraftV2 is now MessageDraft. What was MessageDraft is now MessageDraftV1 (deprecated). The channel.createMessageDraft() method now returns a MessageDraft (the new class). To access the old draft class during a transition period, use channel.createMessageDraftV1().

1// Old primary draft
2const draft = channel.createMessageDraft();
3// draft is MessageDraft (old class)
4
5// New draft
6const draftV2 = channel.createMessageDraftV2();
7// draftV2 is MessageDraftV2

Additional MessageDraft changes

Beyond the class rename, several MessageDraft types changed in 1.0.0:

  • MessageDraft.send() — The options parameter type changed from MessageDraftOptions to SendTextParams.
  • MessageDraft.addMention() — The mention type parameter changed from TextTypes to MentionType.
  • SuggestedMention.type — Changed from TextTypes to MentionType.

Update any code that references these types directly.

Custom events

chat.emitEvent() and chat.listenForEvents() are deprecated. Use channel.emitCustomEvent() to emit a custom event and channel.onCustomEvent() to listen for one.

Note that the JavaScript emitCustomEvent() and onCustomEvent() methods accept an options object rather than positional parameters.

1// Emit
2await chat.emitEvent({
3 channel: "my-channel",
4 type: "custom",
5 payload: { action: "ping" },
6});
7
8// Listen
9const stop = chat.listenForEvents({
10 channel: "my-channel",
11 type: "custom",
12 callback: (event) => {
13 console.log("Custom event:", event.payload);
14 },
15});

GetCurrentUserMentionsResult

Important change

This change affects any code that reads GetCurrentUserMentionsResult. Update it before releasing to users.

The enhancedMentionsData field on GetCurrentUserMentionsResult is renamed to mentions. Its element type changes to a unified UserMention type.

1const result = await chat.getCurrentUserMentions();
2result.enhancedMentionsData.forEach((mentionData) => {
3 // mentionData could be UserMentionData, ChannelMentionData, or ThreadMentionData
4 console.log("Channel:", mentionData.channelId);
5});

New capabilities in 1.0.0

The following features are new in 1.0.0 and have no counterpart in 0.x.x.

FeatureDescription
channel.fetchReadReceipts()
Returns a ReadReceiptsResponse with the last-read timetoken (string) for each channel member. Use for initial load or on-demand checks.
channel.hasMember(userId)
Returns Promise<boolean> — checks whether a user is a member of the channel without fetching the full membership list.
channel.getMember(userId)
Returns Promise<Membership | null> — retrieves a specific member's membership object directly from the channel.
user.isMemberOf(channelId)
Returns Promise<boolean> — checks whether the user is a member of the given channel.
user.getMembership(channelId)
Returns Promise<Membership | null> — retrieves the user's membership for a specific channel.
membership.onUpdated()
Fires when membership metadata changes. Returns () => void.
membership.onDeleted()
Fires when the membership is deleted. Returns () => void.
channel.emitCustomEvent()
Emits a custom event directly on the channel. Accepts (payload, options?: CustomEventEmitOptions). Replaces chat.emitEvent().
channel.onCustomEvent()
Subscribes to custom events on the channel. Accepts (callback, options?: CustomEventListenOptions). Replaces chat.listenForEvents({ type: "custom" }).
user.onMentioned()
Subscribes to mention events for the user. Delivers typed Mention objects.
user.onInvited()
Subscribes to invite events for the user. Delivers typed Invite objects.
user.onRestrictionChanged()
Subscribes to moderation events for the user. Delivers restriction objects with shape { userId, channelId, ban, mute, reason? }.
emitReadReceiptEvents config
ChatConfig.emitReadReceiptEvents ({ public?: boolean; group?: boolean; direct?: boolean }) controls which channel types emit read receipt signals.
ChannelGroup.onMessageReceived()
Subscribes to incoming messages on all channels in the group.
ChannelGroup.onPresenceChanged()
Subscribes to presence events across all channels in the group.
membership.delete()
Removes the membership. Returns Promise<boolean>.
channel.getInvitees()
Returns a list of users invited to the channel.
threadChannel.onThreadMessageReceived()
Subscribes to new messages in a thread. Returns () => void.
threadChannel.onThreadChannelUpdated()
Fires when thread channel metadata changes. Returns () => void.
threadMessage.onThreadMessageUpdated()
Fires when a thread message or its reactions change. Returns () => void.

Custom interface implementations

If your application implements or extends Chat SDK interfaces (such as Channel, User, Message, or Membership), 1.0.0 adds new required methods to these interfaces. Compilation will fail until you implement the new methods.

Key additions include entity-owned callback methods (onUpdated(), onDeleted(), onMessageReceived(), etc.) and new membership/presence methods (hasMember(), getMember(), isMemberOf(), getMembership()).

Review the entity documentation for each interface your code extends to identify the new required members:

Migration steps

To migrate from JavaScript Chat SDK 0.x.x to 1.0.0:

  1. Replace every channel.join(callback, custom?) call. Remove the callback argument from join(). Add channel.onMessageReceived(callback) after a successful join to restore message delivery. Update your code to use Membership directly from the join() result — JoinResult no longer exists.
  2. Rename streaming methods on Channel: connect()onMessageReceived(), getTyping()onTypingChanged(), streamPresence()onPresenceChanged(). Update all disconnect calls: previously joinResult.disconnect(), now the return value of onMessageReceived().
  3. Replace channel.streamUpdates() with channel.onUpdated(). Add channel.onDeleted() wherever you previously detected deletion through the streamUpdates() callback. Note: streamUpdatesOn() is still supported and does not need to be replaced.
  4. Apply the same onUpdated() / onDeleted() pattern to User, Membership, and Message.
  5. Replace channel.streamReadReceipts() with channel.onReadReceiptReceived(). Update the handler to use ReadReceipt (fields: userId, lastReadTimetoken: string) instead of the old dictionary type.
  6. Replace channel.streamMessageReports() with channel.onMessageReported() and update the handler to use the typed MessageReport type.
  7. Replace chat.listenForEvents({ type: "mention" }) with user.onMentioned() and update the handler to use the Mention type.
  8. Replace chat.listenForEvents({ type: "invite" }) with user.onInvited() and update the handler to use the Invite type.
  9. Replace chat.listenForEvents({ type: "moderation" }) with user.onRestrictionChanged() and update the handler to use the restriction object shape { userId, channelId, ban, mute, reason? }.
  10. Update all code that reads Message.reactions. Replace raw object iteration with MessageReaction[] iteration. Access reaction.value, reaction.isMine, and reaction.userIds directly. Remove any references to Message.actions.
  11. Migrate any calls to the old sendText() multi-parameter overload with quotedMessage, mentionedUsers, and files to channel.createMessageDraft() for new integrations. For plain text sends, use the new sendText(text, options?: SendTextParams) form. sendTextLegacy() exists only to ease short-term migration from 0.x.x and will be removed in a future release — do not use it as the primary path for new code.
  12. Replace channel.createMessageDraftV2() calls with channel.createMessageDraft(). Rename any MessageDraftV2 type references to MessageDraft. If you need the old draft during a transitional period, use channel.createMessageDraftV1() and the MessageDraftV1 type.
  13. Replace chat.emitEvent() calls with channel.emitCustomEvent(payload, options?). Replace chat.listenForEvents({ type: "custom" }) calls with channel.onCustomEvent(callback, options?). Remove all references to EventContent types.
  14. Replace result.enhancedMentionsData with result.mentions in any code that calls chat.getCurrentUserMentions(). Update element handling to use UserMention (fields: message, userId, channelId, parentChannelId). Note that message is a Message object, not a timetoken. enhancedMentionsData is deprecated but still available.
  15. Review Chat.init() configuration and add emitReadReceiptEvents if you need non-default read receipt emission behavior.
  16. Remove the soft parameter from all Channel.delete(), User.delete(), Chat.deleteChannel(), and Chat.deleteUser() calls. Update return-type handling to Promise<true>.
  17. Update Membership.update() calls to use the expanded signature (status, type, custom).
  18. Update any code that destructures Message.removeThread() results — the method now returns Promise<boolean>.
  19. Replace message.createThread() (no arguments) with message.createThread(text, options?). Handle the CreateThreadResult return value containing threadChannel and parentMessage.
  20. Update MessageDraft.send() options to use SendTextParams. Replace TextTypes with MentionType in addMention() and SuggestedMention.type.
  21. If you implement Chat SDK interfaces, add the new required methods to your implementations.
  22. If you use streamUpdates() callbacks, be aware that they may now receive null for deletion events. Splitting into separate onUpdated() and onDeleted() handlers avoids this ambiguity.

If you encounter issues during migration, contact PubNub Support.

Last updated on