---
url: https://talkjs.com/docs/Data_APIs/JavaScript/Conversations
title: 'Conversations'
minidoc-source: js
minidoc-lib: data-api
---

A conversation is a chat channel where messages can be sent.
Users can participate in many conversations at once.

## ConversationRef
/** References the current user's view of the conversation with a given conversation ID.
Used in all Data API operations affecting that conversation, such as fetching or updating conversation attributes. Created via Session.conversation().
Important note: ConversationRef is a reference to *the current user's view* of the conversation.
If they are not a participant, "their view" of the conversation does not exist. This means that ConversationRef.get() will return null. Additionally, it means that set() and createIfNotExists() will add the current user as a participant, in order to create "their view" of the conversation. For more details, see the documentation for those methods. */
export interface ConversationRef  {
/** Creates this conversation if it does not already exist. Adds you as a participant in this conversation, if you are not already a participant.
If the conversation already exists or you are already a participant, this operation is still considered successful and the promise will still resolve.
@returns A promise that resolves when the operation completes. The promise rejects if you are not already a participant and client-side conversation syncing is disabled. */
createIfNotExists(params?: CreateConversationParams): Promise<void>;
/** Fetches a snapshot of the conversation.
This contains all of the information related to the conversation and the current user's participation in the conversation.
@returns A snapshot of the current user's view of the conversation, or null if the current user is not a participant (including if the conversation doesn't exist). */
get(): Promise<ConversationSnapshot|null>;
/** The ID of the referenced conversation.
Immutable: if you want to reference a different conversation, get a new ConversationRef instead. */
readonly id: string;
/** Marks the conversation as read.
@returns A promise that resolves when the operation completes. The promise rejects if you are not a participant in the conversation. */
markAsRead(): Promise<void>;
/** Marks the current user as typing in this conversation for 10 seconds.
This means that other users will see a typing indicator in the UI, from the current user.
The user will automatically stop typing after 10 seconds. You cannot manually mark a user as "not typing". Users are also considered "not typing" when they send a message, even if that message was sent from a different tab or using the REST API.
To keep the typing indicator visible for longer, call this function again to reset the 10s timer.
Example implementation
```ts
let lastMarkedAsTyping = 0;
inputElement.addEventListener("change", event=>{
const text = event.target.value;
// Deleting your draft never counts as typing
if (text.length === 0) {
return;
}
const now = Date.now();
// Call `markAsTyping` sparingly - not on every keystroke
// Only call if it has been at least 5 seconds since last time
if (now - lastMarkedAsTyping>5000) {
lastMarkedAsTyping = now;
convRef.markAsTyping();
}
});
// When you send a message, you are no longer considered typing
// So we need to send markAsTyping as soon as you type something
function onSendMessage() {
lastMarkedAsTyping = 0;
}
``` */
markAsTyping(): Promise<void>;
/** Marks the conversation as unread.
@returns A promise that resolves when the operation completes. The promise rejects if you are not a participant in the conversation. */
markAsUnread(): Promise<void>;
/** Get a reference to a message in this conversation
Use this if you need to fetch, delete, or edit a specific message in this conversation, and you know its message ID.
Calling this method multiple times with the same ID reuses the same MessageRef object.
To fetch the most recent messages in this conversation, use ConversationRef.subscribeMessages instead. To send a message, use ConversationRef.send.
@param id - The ID of the message that you want to reference
@returns A MessageRef for the message with that ID in this conversation
@throws
If the id is not a string or is an empty string */
message(id: string): MessageRef;
/** Get a reference to a participant in this conversation
Note that `Participant` is not the same as `User`. A `Participant` represents a user's settings related to a specific conversation.
Calling this method multiple times with the same ID reuses the same ParticipantRef object.
Calling ConversationRef.createIfNotExists or ConversationRef.set will automatically add the current user as a participant.
@param user - Specifies which participant in the conversation you want to reference. Either the user's ID, or a reference to that user.
@returns A ParticipantRef for that user's participation in this conversation
To add "Alice" to the conversation "Cats"
```ts
session.conversation("Cats").participant("Alice").createIfNotExists();
```
The user "Alice" must exist before you do this.
To remove "Alice" from the conversation "Cats"
```ts
session.conversation("Cats").participant("Alice").delete();
```
The user "Alice" will still exist after you do this. This deletes the participant, not the user.
@throws
If the user is not a UserRef or a non-empty string */
participant(user: string|UserRef): ParticipantRef;
/** Sends a message in the conversation
@returns A promise that resolves with a reference to the newly created message. The promise will reject if you do not have permission to send the message.
Send a simple message with markup (bold in this example)
```ts
conversationRef.send("*Hello*");
```
Reply to a message and set custom message data
```ts
conversationRef.send({
text: "Agreed",
referencedMessageId: "...",
custom: { priority: "HIGH" }
});
```
Send pre-formatted text with TextBlock
```ts
conversationRef.send({
content: [{
type: "text",
children: [{
type: "bold",
children: ["Hello"]
}]
}]
});
```
Send a file with SendFileBlock
```ts
// `file` is a File object from `<input type="file">`
const fileToken = await session.uploadImage(
file, { filename: file.name, width: 640, height: 480 }
);
conversationRef.send({
content: [{ type: "file", fileToken }]
});
```
Send a location with LocationBlock
```ts
// You can get the user's location with the browser's geolocation API
const [latitude, longitude] = [42.43, -83.99];
conversationRef.send({
content: [{ type: "location", latitude, longitude }]
});
``` */
send(params: string|SendTextMessageParams|SendMessageParams): Promise<MessageRef>;
/** Sets properties of this conversation and the current user's participation.
If this conversation does not already exist, it will be created.
If the user is not currently a participant in this conversation, they will be added.
@returns A promise that resolves when the operation completes. When client-side conversation syncing is disabled, you must already be a participant and you cannot set anything except the `notify` property. Everything else requires client-side conversation syncing to be enabled, and will cause the promise to reject. */
set(params: SetConversationParams): Promise<void>;
/** Subscribes to the conversation.
While the subscription is active, `onSnapshot` will be called when you join or leave the conversation, or when the snapshot changes. This includes changes to nested data. As an extreme example, `onSnapshot` would be called when `snapshot.lastMessage.referencedMessage.sender.name` changes.
The snapshot is null if you are not a participant in the conversation (including when the conversation doesn't exist)
Remember to call `.unsubscribe` on the subscription once you are done with it. */
subscribe(onSnapshot?: (snapshot: ConversationSnapshot|null)=>void): ConversationSubscription;
/** Subscribes to the messages in the conversation.
While the subscription is active, `onSnapshot` will be called whenever the message snapshots change. This includes when a message is sent, edited, deleted, and when you load more messages. It also includes when nested data changes, such as when `snapshot[0].referencedMessage.sender.name` changes. `loadedAll` is true when `snapshot` contains all the messages in the conversation, and false if you could load more.
The `snapshot` list is ordered chronologically with the most recent messages at the start. When a new message is received, it will be added to the start of the list.
The snapshot is null if you are not a participant in the conversation (including when the conversation doesn't exist)
Initially, you will be subscribed to the 30 most recent messages and any new messages. Call `loadMore` to load additional older messages. This will trigger `onSnapshot`.
Tip: If you only care about the most recent message in the conversation, use `ConversationRef.subscribe` or `Session.subscribeConversations`. Then use the `ConversationSnapshot.lastMessage` property. This is easier to use and slightly more efficient.
Remember to call `.unsubscribe` on the subscription once you are done with it. */
subscribeMessages(onSnapshot?: (snapshot: MessageSnapshot[]|null, loadedAll: boolean)=>void): MessageSubscription;
/** Subscribes to the participants in the conversation.
While the subscription is active, `onSnapshot` will be called whenever the participant snapshots change. This includes when someone joins or leaves, when their participant attributes are edited, and when you load more participants. It also includes when nested data changes, such as when `snapshot[0].user.name` changes. `loadedAll` is true when `snapshot` contains all the participants in the conversation, and false if you could load more.
The `snapshot` list is ordered chronologically with the participants who joined most recently at the start. When someone joins the conversation, they will be added to the start of the list.
The snapshot is null if you are not a participant in the conversation (including when the conversation doesn't exist)
Initially, you will be subscribed to the 10 participants who joined most recently, and any new participants. Call `loadMore` to load additional older participants. This will trigger `onSnapshot`.
Remember to call `.unsubscribe` on the subscription once you are done with it. */
subscribeParticipants(onSnapshot?: (snapshot: ParticipantSnapshot[]|null, loadedAll: boolean)=>void): ParticipantSubscription;
/** Subscribes to the typing status of the conversation.
While the subscription is active, `onSnapshot` will be called when you join or leave the conversation, or when the snapshot changes. This includes changes to the nested `UserSnapshot`s. If one of the people who is typing, changes their name, `onSnapshot` will be called. You will not be notified when there are already "many" people typing, and another person starts typing, because the snapshot does not change.
The snapshot is null if you are not a participant in the conversation (including when the conversation doesn't exist)
Remember to call `.unsubscribe` on the subscription once you are done with it. */
subscribeTyping(onSnapshot?: (snapshot: TypingSnapshot|null)=>void): TypingSubscription;
}

## CreateConversationParams
/** Parameters you can pass to ConversationRef.createIfNotExists.
Properties that are `undefined` will be set to the default. */
export interface CreateConversationParams  {
/** Your access to the conversation. Default = "ReadWrite" access. */
access?: "Read"|"ReadWrite";
/** Custom metadata to set on the conversation. Default = no custom metadata */
custom?: Record<string, string>;
/** Your notification settings. Default = `true` */
notify?: boolean|"MentionsOnly";
/** The URL for the conversation photo to display in the chat header. Default = no photo, show a placeholder image. */
photoUrl?: string;
/** The conversation subject to display in the chat header. Default = no subject, list participant names instead. */
subject?: string;
/** System messages which are sent at the beginning of a conversation. Default = no messages.
Welcome messages are rendered in the UI as messages, but they are not real messages. This means they do not appear when you list messages using the REST API or JS Data API, and you cannot reply or react to them. */
welcomeMessages?: string[];
}

## SetConversationParams
/** Parameters you can pass to ConversationRef.set.
Properties that are `undefined` will not be changed. To clear / reset a property to the default, pass `null`. */
export interface SetConversationParams  {
/** Your access to the conversation. Default = "ReadWrite" access. */
access?: "Read"|"ReadWrite"|null;
/** Custom metadata you have set on the conversation. This value acts as a patch. Remove specific properties by setting them to `null`. Default = no custom metadata */
custom?: Record<string, string|null>|null;
/** Your notification settings. Default = `true` */
notify?: boolean|"MentionsOnly"|null;
/** The URL for the conversation photo to display in the chat header. Default = no photo, show a placeholder image. */
photoUrl?: string|null;
/** The conversation subject to display in the chat header. Default = no subject, list participant names instead. */
subject?: string|null;
/** System messages which are sent at the beginning of a conversation. Default = no messages.
Welcome messages are rendered in the UI as messages, but they are not real messages. This means they do not appear when you list messages using the REST API or JS Data API, and you cannot reply or react to them. */
welcomeMessages?: string[]|null;
}

## SendTextMessageParams
/** Parameters you can pass to ConversationRef.send.
Properties that are `undefined` will be set to the default.
This is a simpler version of SendMessageParams that only supports text messages. */
export interface SendTextMessageParams  {
/** Custom metadata to set on the message. Default = no custom metadata */
custom?: Record<string, string>;
/** The message that you are replying to. Default = not a reply */
referencedMessage?: string|MessageRef;
/** The text to send in the message.
This is parsed the same way as the text entered in the message field. For example, `*hi*` will appear as `hi` in bold.
See the message formatting documentation for more details. */
text: string;
}

## SendMessageParams
/** Parameters you can pass to ConversationRef.send.
Properties that are `undefined` will be set to the default.
This is the more advanced method for editing a message, giving full control over the message content. You can decide exactly how a text message should be formatted, edit an attachment, or even turn a text message into a location. */
export interface SendMessageParams  {
/** The most important part of the message, either some text, a file attachment, or a location.
By default users do not have permission to send LinkNode, ActionLinkNode, or ActionButtonNode, as they can be used to trick the recipient.
Currently, each message can only contain a single SendContentBlock. */
content: [SendContentBlock];
/** Custom metadata to set on the message. Default = no custom metadata */
custom?: Record<string, string>;
/** The message that you are replying to. Default = not a reply */
referencedMessage?: string|MessageRef;
}

## ConversationSnapshot
/** A snapshot of a conversation's attributes at a given moment in time.
Also includes information about the current user's view of that conversation, such as whether or not notifications are enabled.
Snapshots are immutable and we try to reuse them when possible. You should only re-render your UI when `oldSnapshot !== newSnapshot`. */
export interface ConversationSnapshot  {
/** The current user's permission level in this conversation. */
readonly access: "Read"|"ReadWrite";
/** The date that the conversation was created, as a unix timestamp in milliseconds. */
readonly createdAt: number;
/** Custom metadata you have set on the conversation */
readonly custom: Record<string, string>;
/** Everyone in the conversation has read any messages sent on or before this date.
This is the minimum of all the participants' `readUntil` values. Any messages sent on or before this timestamp should show a "read" indicator in the UI.
This value will rarely change in very large conversations. If just one person stops checking their messages, `everyoneReadUntil` will never update. */
readonly everyoneReadUntil: number;
/** The ID of the conversation */
readonly id: string;
/** Whether the conversation should be considered unread.
This can be true even when `unreadMessageCount` is zero, if the user has manually marked the conversation as unread. */
readonly isUnread: boolean;
/** The date that the current user joined the conversation, as a unix timestamp in milliseconds. */
readonly joinedAt: number;
/** The last message sent in this conversation, or null if no messages have been sent. */
readonly lastMessage: MessageSnapshot|null;
/** The current user's notification settings for this conversation.
`false` means no notifications, `true` means notifications for all messages, and `"MentionsOnly"` means that the user will only be notified when they are mentioned with an `@`. */
readonly notify: boolean|"MentionsOnly";
/** Contains the URL of a photo to represent the topic of the conversation or `null` if the conversation does not have a photo specified. */
readonly photoUrl: string|null;
/** The most recent date that the current user read the conversation.
This value is updated whenever you read a message in a chat UI, open an email notification, or mark the conversation as read using an API like ConversationRef.markAsRead.
Any messages sent after this timestamp are unread messages. */
readonly readUntil: number;
/** Contains the conversation subject, or `null` if the conversation does not have a subject specified. */
readonly subject: string|null;
/** The number of messages in this conversation that the current user hasn't read. */
readonly unreadMessageCount: number;
/** One or more welcome messages that will be rendered at the start of this conversation as system messages.
Welcome messages are rendered in the UI as messages, but they are not real messages. This means they do not appear when you list messages using the REST API or JS Data API, and you cannot reply or react to them. */
readonly welcomeMessages: string[];
}

## ConversationSubscription
/** A subscription to a specific conversation.
Get a ConversationSubscription by calling ConversationRef.subscribe
Remember to `.unsubscribe` the subscription once you are done with it. */
export interface ConversationSubscription  {
/** Resolves when the subscription starts receiving updates from the server. */
readonly connected: Promise<ConversationActiveState>;
/** The current state of the subscription
An object with the following fields:
`type` is one of "pending", "active", "unsubscribed", or "error".
When `type` is "active", includes `latestSnapshot: ConversationSnapshot|null`, the current state of the conversation. `latestSnapshot` is `null` when you are not a participant or the conversation does not exist.
When `type` is "error", includes the `error` field. It is a JS `Error` object explaining what caused the subscription to be terminated. */
state: PendingState|ConversationActiveState|UnsubscribedState|ErrorState;
/** Resolves when the subscription permanently stops receiving updates from the server.
This is either because you unsubscribed or because the subscription encountered an unrecoverable error. */
readonly terminated: Promise<UnsubscribedState|ErrorState>;
/** Unsubscribe from this resource and stop receiving updates.
If the subscription is already in the "unsubscribed" or "error" state, this is a no-op. */
unsubscribe(): void;
}

## ConversationListSubscription
/** A subscription to your most recently active conversations.
Get a ConversationListSubscription by calling Session.subscribeConversations.
The subscription is 'windowed'. Initially, this window contains the 20 most recent conversations. Conversations are ordered by last activity. The last activity of a conversation is either `joinedAt` or `lastMessage.createdAt`, whichever is higher.
The window will automatically expand to include any conversations you join, and any old conversations that receive new messages after subscribing.
You can expand this window by calling ConversationListSubscription.loadMore, which extends the window further into the past.
Remember to `.unsubscribe` the subscription once you are done with it. */
export interface ConversationListSubscription  {
/** Resolves when the subscription starts receiving updates from the server.
Wait for this promise if you want to perform some action as soon as the subscription is active.
The promise rejects if the subscription is terminated before it connects. */
readonly connected: Promise<ConversationListActiveState>;
/** Expand the window to include older conversations
The `count` parameter is relative to the current number of loaded conversations. If you call `loadMore(5)` and then call `loadMore(10)` immediately afterwards (without awaiting the promise), then only 10 additional conversations will be loaded, not 15.
Avoid calling `.loadMore` in a loop until you have loaded all conversations. This is usually unnecessary: any time a conversation receives a message, it appears at the start of the list of conversations. If you do need to call loadMore in a loop, make sure you set a small upper bound (e.g. 100) on the number of conversations, where the loop will exit.
@param count - The number of additional conversations to load. Must be between 1 and 30. Default 20.
@returns A promise that resolves once the additional conversations have loaded */
loadMore(count?: number): Promise<void>;
/** The current state of the subscription
An object with the following fields:
`type` is one of "pending", "active", "unsubscribed", or "error".
When `type` is "active", includes `latestSnapshot` and `loadedAll`.
- `latestSnapshot: ConversationSnapshot[]` the current state of the conversations in the window
- `loadedAll: boolean` true when `latestSnapshot` contains all the conversations you are in
When `type` is "error", includes the `error` field. It is a JS `Error` object explaining what caused the subscription to be terminated. */
state: PendingState|ConversationListActiveState|UnsubscribedState|ErrorState;
/** Resolves when the subscription permanently stops receiving updates from the server.
This is either because you unsubscribed or because the subscription encountered an unrecoverable error. */
readonly terminated: Promise<UnsubscribedState|ErrorState>;
/** Unsubscribe from this resource and stop receiving updates.
If the subscription is already in the "unsubscribed" or "error" state, this is a no-op. */
unsubscribe(): void;
}

## TypingSnapshot
/** A snapshot of the typing indicators in a conversation at a given moment in time. Will be either FewTypingSnapshot when only a few people are typing, or ManyTypingSnapshot when many people are typing.
Currently FewTypingSnapshot is used when there are 5 or less people typing in the conversation, and ManyTypingSnapshot is used when more than 5 people are typing. This limit may change in the future, which will not be considered a breaking change.
Converting a TypingSnapshot to text
```ts
function formatTyping(snapshot: TypingSnapshot): string {
if (snapshot.many) {
return "Several people are typing";
}
const names = snapshot.users.map(user=>user.name);
if (names.length === 0) {
return "";
}
if (names.length === 1) {
return names[0] + " is typing";
}
if (names.length === 2) {
return names.join(" and ") + " are typing";
}
// Prefix last name with "and "
names.push("and " + names.pop());
return names.join(", ") + " are typing";
}
``` */
export type TypingSnapshot = FewTypingSnapshot|ManyTypingSnapshot;

## FewTypingSnapshot
/** The TypingSnapshot variant used when only a few people are typing. */
export interface FewTypingSnapshot  {
/** Check this to differentiate between FewTypingSnapshot (`false`) and ManyTypingSnapshot (`true`).
When `false`, you can see the list of users who are typing in the `users` property. */
readonly many: false;
/** The users who are currently typing in this conversation.
The list is in chronological order, starting with the users who have been typing the longest. The current user is never contained in the list, only other users. */
readonly users: UserSnapshot[];
}

## ManyTypingSnapshot
/** The TypingSnapshot variant used when many people are typing. */
export interface ManyTypingSnapshot  {
/** Check this to differentiate between FewTypingSnapshot (`false`) and ManyTypingSnapshot (`true`).
When `true`, you do not receive a list of users who are typing. You should show a message like "several people are typing" instead. */
readonly many: true;
}

## TypingSubscription
/** A subscription to the typing status in a specific conversation
Get a TypingSubscription by calling ConversationRef.subscribeTyping.
When there are "many" people typing (meaning you received ManyTypingSnapshot), the next update you receive will be FewTypingSnapshot once enough people stop typing. Until then, your ManyTypingSnapshot is still valid and does not need to changed, so `onSnapshot` will not be called.
Remember to `.unsubscribe` the subscription once you are done with it. */
export interface TypingSubscription  {
/** Resolves when the subscription starts receiving updates from the server.
Wait for this promise if you want to perform some action as soon as the subscription is active.
The promise rejects if the subscription is terminated before it connects. */
readonly connected: Promise<TypingActiveState>;
/** The current state of the subscription
An object with the following fields:
`type` is one of "pending", "active", "unsubscribed", or "error".
When `type` is "active", includes `latestSnapshot: TypingSnapshot|null`. It is the current state of the typing indicators, or null if you're not a participant in the conversation
When `type` is "error", includes the `error: Error` field. It is a JS `Error` object explaining what caused the subscription to be terminated. */
state: PendingState|TypingActiveState|UnsubscribedState|ErrorState;
/** Resolves when the subscription permanently stops receiving updates from the server.
This is either because you unsubscribed or because the subscription encountered an unrecoverable error. */
readonly terminated: Promise<UnsubscribedState|ErrorState>;
/** Unsubscribe from this resource and stop receiving updates.
If the subscription is already in the "unsubscribed" or "error" state, this is a no-op. */
unsubscribe(): void;
}

## ConversationSearch
/** Search object returned by `TalkSession.searchConversations` functions. Used to search for conversations the user is in. */
export interface ConversationSearch  {
/** Continues searching for more conversations.
You can only call `loadMore` when the search has a query set, and is idle. Calling `loadMore` before calling `setQuery`, while the `setQuery` promise is still pending, or while another `loadMore` promise is pending, has no effect.
@param limit - The number of results to load. `limit` should be between 1 and 50. Default 10.
@returns A promise that resolves once finished loading, containing all the results from this page and all previous pages. The promise rejects if the search encounters an error. */
loadMore(limit?: number): Promise<ConversationSearchState>;
/** Sets the query string for this search and starts searching for conversations whose `subject` or `custom.search` match the query.
This resets the search to its initial state, clearing all previous results. Initially loads 3 results. Call MessageSearch.loadMore to load additional results.
The query matches a conversation when, for each word in the query, that word appears in the conversation's subject or in `conversation.custom.search`. This means one of the words in the string starts with the word from the query. This is case-insensitive and punctuation at the start / end of words is ignored (in both the string and the query).
A conversation titled `I like to look at rainbows`, will appear in searches for `rainbows`, `rain`, `look at`, `(look, i *LIKE* rain)`, and `look look look!`. It will not appear in searches for `bows`, `rain-bows`, `look-at`, or `I like to look at rainbows too`.
It is not possible to match both a conversation's subject and its `custom.search` field at the same time. A conversation called `Kittens` with `custom.search: "where to buy food"` will not appear in a search for `kitten food`. However, this only applies per-conversation. If there is one conversation with subject `Kittens` and a second conversation with `custom.search: "Kittens"`, then a search for `kittens` will return both conversations.
@returns A promise that resolves with the complete first page of results. The promise rejects if the search encounters an error. */
setQuery(query: string): Promise<ConversationSearchState>;
}

## ConversationSearchState
/** The state of a conversation search in the current step. */
export interface ConversationSearchState  {
/** Whether all results have been loaded.
Note: When true, this means that the search has reached the oldest matching conversation and no more conversations can be loaded. However, it does not mean that `results` contains every matching conversation. Any conversations that received messages since you called `setQuery`, may be missing from `results`. */
loadedAll: boolean;
/** The conversations that were found in the search. */
results: ConversationSnapshot[];
}