Skip to content

Build a quickstart chat app

Follow this tutorial to build a quickstart chat app with XMTP.

When you build your quickstart app, you will use a key pair to create an identity and register it with XMTP. This gives you an inbox ID, which is a stable identifier that serves as the destination for your messages.

Follow Install the XMTP SDK to install SDK 8.0.0-rc1. From your app directory, install the package for your platform:

Terminal window
npm install --save-exact @xmtp/browser-sdk@8.0.0-rc1

The signer provides identity(), kind(), and sign(request). Its sign method returns a Signature with the raw signature bytes. The SDK’s Client.generateKeys() (SDKClient.generateKeys() in Kotlin and Swift) returns a local signing key that implements these methods. For a persistent account, save and restore the private key before opening the client. The examples below create temporary accounts. The backend URL is required. Use the URL of your running backend. These examples create temporary identities for testing. Do not use their key lifecycle in a production app: store the signer key and database encryption key securely and reuse them with the same database on later launches.

The Browser and Node blocks come from complete source files. They are checked against the SDK types during the docs build. The mobile examples create a second client so that the message recipient is registered on the same backend.

import {
class Client

An XMTP client. Its inputs and results are public values; the binding Client and the target transport stay private to the package.

Client
} from "@xmtp/browser-sdk";
// Use a new signer and an in-memory database for this local test.
const
const signer: LocalSigningKey
signer
= await
class Client

An XMTP client. Its inputs and results are public values; the binding Client and the target transport stay private to the package.

Client
.
ClientMembers.generateKeys(): Promise<LocalSigningKey>
generateKeys
();
const
const client: Client
client
= await
class Client

An XMTP client. Its inputs and results are public values; the binding Client and the target transport stay private to the package.

Client
.
Client.create(signer: Signer, options: ClientOptions): Promise<Client>
create
(
const signer: LocalSigningKey
signer
, {
backend?: BackendSource | undefined
backend
: {
url: string
url
: "http://127.0.0.1:5050" },
storage: StorageOptions
storage
: {
location: StorageLocation
location
: "inMemory" },
});
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
("Your inbox ID:",
const client: Client
client
.
ClientMembers.inboxId: string
inboxId
);

Create a conversation and send a text message. For Browser and Node, use the inbox ID printed by a second running client on the same backend. Put the Kotlin send and stream operations in the withDemoClients block. For Swift, use the recipient client from the preceding step.

const
const recipientInboxId: string | null
recipientInboxId
=
var window: Window & typeof globalThis

The window property of a Window object points to the window object itself.

MDN Reference

window
.
function prompt(message?: string, _default?: string): string | null

window.prompt() instructs the browser to display a dialog with an optional message prompting the user to input some text, and to wait until the user either submits the text or cancels the dialog.

MDN Reference

prompt
("Recipient inbox ID");
if (!
const recipientInboxId: string | null
recipientInboxId
) throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
("Enter a recipient inbox ID");
const
const group: Group
group
= await
const client: Client
client
.
ClientMembers.conversations: Conversations
conversations
.
Conversations.createGroup(members: Array<InboxId> | Array<PublicIdentity>, options?: CreateGroupOptions): Promise<Group>
createGroup
([
const recipientInboxId: string
recipientInboxId
]);
await
const group: Group
group
.
Group.sendText(text: string, options?: SendOptions): Promise<MessageId>
sendText
("Hello everyone");

Sending a message stores it locally and publishes it to the backend. Update your UI from the message stream or query the conversation’s messages. See optimistic sending to show a message before publication completes.

Use client.conversations.streamAllMessages() in each SDK to receive messages. Use syncAll to fetch welcomes and messages in the selected conversations. The JavaScript examples select allowed conversations; allow an incoming conversation before you expect its messages in that stream. See Consent.

const
const stream: MessageStream
stream
=
const client: Client
client
.
ClientMembers.conversations: Conversations
conversations
.
Conversations.streamAllMessages(options?: MessageStreamOptions): MessageStream
streamAllMessages
({
consentStates?: readonly ConsentState[] | undefined
consentStates
: ["allowed"],
});
const
const receive: Promise<void>
receive
= (async () => {
for await (const
const message: Message
message
of
const stream: MessageStream
stream
)
var console: Console
console
.
Console.log(...data: any[]): void

The console.log() static method outputs a message to the console.

MDN Reference

log
("New message:",
const message: Message
message
);
})();
await
const client: Client
client
.
ClientMembers.conversations: Conversations
conversations
.
Conversations.syncAll(consentStates: Array<ConsentState> | undefined): Promise<GroupSyncSummary>
syncAll
(["allowed"]);
// Call stream.return() and await receive when you stop the stream.

When you stop the JavaScript example, call await stream.return(), await receive, then call await client.end(). On mobile, stop the message stream and end both clients. The Kotlin helper does this when its block exits; use try await recipient.end() and try await client.end() in Swift.