Skip to content

Build group chat with MLS and XMTP

Secure group chat is an important part of every messaging app. XMTP group chat is based on the MLS specification.

To learn more, see Security and encryption with MLS and XMTP.

In this guide, we cover the essentials of building group chat using XMTP, from the initial step of ensuring that potential members have v3 identities to starting a new group chat to synchronizing group chat details.

Overview

Here are some key points to understand before building group chat with XMTP.

Group chat keys work per app installation

An app installation is registered to a user. Group chat messages a user sends using an app installation are encrypted so only that app installation can decrypt them. This is because keys are generated to work per app installation. App installations do not share the same keys.

To learn more, see Installations.

⚠️ Important: Manage actions that make a local database inaccessible

Because group chat keys work per app installation, there are user actions that make an app installation’s local database inaccessible to other app installations.

Here are the actions:

  • A user logs out of an installation of your app and logs into a different app installation on their device.
  • A user deletes an installation of your app from their device.

As a result of either of these actions, the user will lose access to the local database for the app installation, which includes all group chat messages they sent using the installation of your app on their device.

As an app developer, this concept is important to understand and communicate to your users. For example, you might consider using this language:

If you log out of <app name> and log into a different app on this device, or delete <app name> from this device, you will lose access to all group chat messages you sent using this installation of <app name> on this device.

To enable your users to avoid losing access to their local databases, allow them to store their local cache in iCloud or Google Cloud, for example. This option will enable message persistence within a single app ecosystem.

For example, let's say that App A enables users to store their local cache in iCloud. A user does this and then deletes App A from their device. The user can reinstall App A and restore their local cache from iCloud.

However, this option does not allow users to restore a local cache across apps. For example, a local cache from App A can't be used to restore message history to App B. In this case, the best solution will be the forthcoming XMTP Message History server.

Web support for group chat

The XMTP JavaScript SDK and React SDK don’t yet provide web support for group chat. However, if you want to integrate group chat backend support, you can use the experimental XMTP Node SDK.

Web support for group chat is lagging due to technical challenges related to integrating WebAssembly (WASM) and its extensions. WASM sandbox cannot access the operating system directly, which is necessary for tasks like file reads. WASI extends WASM to allow operating system interaction, but it comes with its own set of challenges. For example, current WASI 0.2 has no built-in way to handle asynchronous operations effectively. The team is working toward solutions.

Group size

Group sizes can range from 1 to 400. XMTP expects most groups will fall into one of two profiles:

  • Small groups where everyone knows each other (e.g., texting) and in which the group size is not large enough to require a lot of permissions or moderation.

  • Large groups of relative strangers (e.g., Discord) that often start with restricted permission and more moderation to ensure the group is not too noisy for its intended purpose.

Also, a one-person group is allowed. Messages sent to the group will be broadcast to all the installations used by that person.

Local database creation and syncing

When an app first calls Client.create() in one of the XMTP APIs, LibXMTP creates a local database to manage messaging between the app and the network. In subsequent calls, it loads the existing database. The database is encrypted using the keys from the Signer interface. See Saving keys for instructions on how to extract the key, store it, and pass it to Client.create() later. If you want to delete your local database, call Client.deleteLocalDatabase().

Calling the sync() method on the Conversations protocol checks for welcome messages and creates a new group if one is found. The new group will not show up on the list until the app syncs. Calling the sync() method on the Group protocol prompts LibXMTP to pull updates from the network and push any unsent updates from the client related to the group. This will update the database for the group.

In some cases, LibXMTP will perform a sync automatically. This currently includes calls to add/remove members to a group or using the stream functions. Outside of those methods, apps can call sync() when they choose to efficiently manage network traffic.

When a message is sent, it is stored in the local database with a status of Unpublished. find_messages() will display unpublished messages by default unless you filter on DeliveryStatus. Unpublished messages can be displayed to the user but should be marked as not yet sent in the app UI.

When the app calls sync(), LibXMTP will make three attempts to automatically send the message to the network. If all three attempts fail, the message status will be marked as Failed. If it succeeds, it will be marked as Published.

To learn more about database operations, see the XMTP MLS README.

Push notifications

Group chat supports push notifications. To learn more, see the Build push notifications.

Example apps

If you’d like to dive right into exploring the code, check out the example apps in these XMTP SDKs that support group chat:

Create group chats

Take these steps to create group chats.

Create a client that supports group chat

Check if a user has a V3 identity

Only users with V3 identities can participate in a group chat. For this reason, the first step to creating a group chat is to check if a potential group member has a V3 identity.

To learn more about V3 identities, see Multi-wallet Identity in V3.

React Native
const canMessageV3 = await client.canGroupMessage([alix.address, bo.address]);

Create a group chat

To create a group chat, each of the specified member addresses must have a V3 identity and have used the identity to start up an app installation that supports group chat.

React Native
const group = await client.conversations.newGroup(
  [anotherClient.address],
  // set the permission level for the group. Options include "admin_only", where only the creator is an admin, or "all_members" to make everyone an admin.
  {
    permissionLevel: "admin_only",
    name: "Group Name",
    imageUrlSquare: "<URL>",
  },
);

Display group chats

Use these methods to display group chats associated with the current client.

Get group chats

JavaScript
//First fetch new data from the network
await client.conversations.syncGroups();
//Get the updated group list
const groups =await client.conversations.listGroups();

Get group chats and direct message conversations:

JavaScript
// List all conversations, including both group and individual
val conversations = client.conversations.listAll()

Get group chat messages

React Native
await group.messages();

Check if user is active in a group chat

Use the isActive property to check if the current user is still a participant in a group chat. For example, if a user is removed from a group chat, the group chat will not be active for the user.

Use this status to adjust the user’s interface accordingly, such as removing the user’s ability to send messages in the group chat.

React Native
const isActive = await group.isActive();

Send messages in group chats

Group chat supports all message types you can send using direct message conversations, including Subscription Frames, replies, reactions, attachments, and read receipts.

React Native
const group =await client.conversations.newGroup([
  walletAddress1,
  walletAddress2,
]);
// Send a message
await group.send("Hello, group!");

Stream group chats

Use these methods to stream group chats.

Stream group chat updates

React Native
// Listen for group chat updates
const streamGroups =async (client) => {
const groups = [];
const cancelStreamGroups =await client.conversations.streamGroups(
(group) => {
groups.push(group);
	},
);
 
// Use cancelStreamGroups() to stop listening to group updates
};

Stream group chats and direct message conversations:

React Native
const streamAllGroupMessages = async (client) => {
  const allConvos = [];
  const cancelStreamAllGroupMessages =
    await client.conversations.streamAllMessages(async (message) => {
      console.log(`New message: ${message.content}`);
    });
  // Use cancelStreamAllGroupMessages() to stop listening to all conversation updates
};

Stream group chat messages and membership updates

Stream messages and member management updates in group chats, such as adding and removing members:

React Native
// Assuming `group` is an existing group chat object
const streamGroupMessages =async (group) => {
const cancelGroupMessageStream =await group.streamGroupMessages(
async (message) => {
      console.log(`New message: ${message.content}`);
      // Membership updates
if (message.contentTypeId === ContentTypes.GroupMembershipChange) {
const addresses =await group.memberAddresses();
        // Get new members
        console.log(addresses); // Example usage of addresses
      }
    },
  );
 
  // Use cancelGroupMessageStream() to stop listening to group updates
return cancelGroupMessageStream;
};

Stream messages in group chats and direct message conversations:

React Native
const streamAllGroupMessages =async (client) => {
const allConvos = [];
const cancelStreamAllGroupMessages =
await client.conversations.streamAllMessages(
			async (message) => {
      console.log(`New message: ${message.content}`);
    });
  // Use cancelStreamAllGroupMessages() to stop listening to all conversation updates
};

Sync group chats

Calling sync() for a group or groups gets any updates since the last sync and adds them to the local database. Be sure to periodically synchronize each group chat to ensure your app has the latest group chat details, including the most recent messages, member list, and group chat details, for example.

Updates are also retrieved and added to the local database when streaming and when the user takes an action.

When your user sends a message, you don’t need to sync with the network for them to see their own message. The message gets written to their local database, and it shows up immediately for them. The same applies when your user creates a group.

See ⚠️ Important: Manage actions that make a local database inaccessible.

This means that everything XMTP gets from the network for the user is stored in this local database and never needs to be fetched again. Extra syncing isn’t costly as the process won’t fetch data it already has, but this is just an explanation of why syncing isn’t necessary for data created by a user’s own actions.

However, you must sync (or use streaming) to enable other users to see the group chats and messages your user created and sent.

Sync group chat updates

React Native
// List groups without syncing with the network
let groups = await client.conversations.listGroups();
// groups length might be 0 if not synced after group creation
// Sync groups and list again
await client.conversations.syncGroups();
groups = await client.conversations.listGroups();
console.log(groups.length); // groups length reflects the actual number of groups

Synchronize group chats and direct message conversations:

React Native
let conversations = await client.conversations.listAll();

Sync group chat messages and membership updates

Use sync() to synchronize group chat data, such as new messages or membership changes.

React Native
// Assume group is an existing group chat object
await group.sync(); // Synchronizes the group's messages and members
// Fetch messages without network sync
const messages =await group.messages(true);
console.log(messages.length); // Shows messages fetched from local data

SDK method call flows

Form a group chat

Forming a group

Forming groups is fairly simple. Alix's app can use the conversation protocol to create a group with Bo and Caro. Behind the scenes, XMTP looks up every installation belonging to Bo and Caro and sends each installation a welcome message. Each installation has its own key pair, so each message is encrypted separately.

Bo and Caro's apps then see the new conversation on the list when they use the conversation protocol to sync with XMTP. They then use the group protocol to engage with individual groups.

Send and receive group chat messages

Sending and receiving

When Alix sends a message to the group, XMTP sends the message to every installation of every group member. Other group member's apps use the group protocol to receive the message.