Adapter
A ChatAdapter is how BYO mode talks to your store. Helper: createLocalAdapter (demo). Hosted mode is used internally when you pass appId + publishableKey and omit adapter.
Resolve order
- If
adapteris passed, use it (wins over Hosted props). - Else if
appIdandpublishableKeyare set, use Hosted. - Else fall back to
createLocalAdapter.
Modes and pricing: Modes.
Required methods
listConversations({ appId, viewerId, q?, cursor? })
createConversation({ appId, kind, participantIds, title?, author, participants? })
listMessages({ conversationId, cursor?, viewerId })
sendMessage({ conversationId, body, author, idempotencyKey?, attachmentIds? })
editMessage({ messageId, body, author })
deleteMessage({ messageId, author })
markRead({ conversationId, author })Optional: addMembers (optional participants names, same ids), removeMember, leave, archive, subscribe, uploadAttachment, searchMessages. Names are optional; the widget still displays from people if your API ignores them. Optional authorToken / viewerToken fields exist for Hosted-style assertions; on BYO, enforce authz from your session instead of trusting the browser. See Identity.
BYO example
<Chat
appId="cha_..."
user={currentUser}
adapter={{
listConversations: async (input) =>
fetch(`/api/chat/conversations?q=${input.q ?? ""}`).then((r) => r.json()),
createConversation: async (input) =>
fetch("/api/chat/conversations", { method: "POST", body: JSON.stringify(input) }).then((r) => r.json()),
listMessages: async (input) =>
fetch(`/api/chat/conversations/${input.conversationId}/messages`).then((r) => r.json()),
sendMessage: async (input) =>
fetch(`/api/chat/conversations/${input.conversationId}/messages`, {
method: "POST",
body: JSON.stringify(input),
}).then((r) => r.json()),
editMessage: async (input) =>
fetch(`/api/chat/messages/${input.messageId}`, { method: "PATCH", body: JSON.stringify(input) }).then((r) => r.json()),
deleteMessage: async (input) =>
fetch(`/api/chat/messages/${input.messageId}`, {
method: "POST",
body: JSON.stringify({ action: "delete", ...input }),
}).then((r) => r.json()),
markRead: async (input) => {
await fetch(`/api/chat/conversations/${input.conversationId}/read`, {
method: "POST",
body: JSON.stringify(input),
});
},
}}
/>Routes are illustrative. Enforce authz on your API using the signed-in session. Re-bind authorship from the session. Do not trust client-supplied author.id alone. Direct conversations should be unique per pair. Groups max 20.
Limits and content
- Body max 4,000 characters (plain text with light markup in the widget only).
- Return the conversation and message shapes the widget expects (ids, members, timestamps, unreadCount).
- BYO does not emit Bootstrapware
chat.*webhooks. Emit your own from your API if needed. - Bodies and file bytes stay on your backend. Never send them to Bootstrapware MCP.
Walkthrough: BYO vs Hosted.
Related: Modes · Identity · Conversations · Attachments · Quickstart