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

  1. If adapter is passed, use it (wins over Hosted props).
  2. Else if appId and publishableKey are set, use Hosted.
  3. 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