Matrix adapter for Chat SDK. It runs over Matrix sync instead of webhooks and works with Beeper conversations and bridged networks such as WhatsApp, Telegram, Instagram, and Signal.
Requires Node.js >=22.
pnpm add chat @beeper/chat-adapter-matrixcreateMatrixAdapter() reads its config from environment variables when called without arguments.
import { Chat } from "chat";
import { createMemoryState } from "@chat-adapter/state-memory";
import { createMatrixAdapter } from "@beeper/chat-adapter-matrix";
const matrix = createMatrixAdapter();
const bot = new Chat({
userName: process.env.MATRIX_BOT_USERNAME ?? "beeper-bot",
state: createMemoryState(),
adapters: { matrix },
});
bot.onNewMention(async (thread, message) => {
await thread.subscribe();
await thread.post(`Hi ${message.author.userName}. Mention me or run /ping.`);
});
bot.onSlashCommand("/ping", async (event) => {
await event.channel.post("pong");
});
await bot.initialize();Chat SDK concepts such as Chat, Thread, Message, subscriptions, and handlers work the same here. See the upstream docs for the core API:
createMatrixAdapter({
baseURL: process.env.MATRIX_BASE_URL!,
auth: {
type: "accessToken",
accessToken: process.env.MATRIX_ACCESS_TOKEN!,
userID: process.env.MATRIX_USER_ID,
},
});createMatrixAdapter({
baseURL: process.env.MATRIX_BASE_URL!,
auth: {
type: "password",
username: process.env.MATRIX_USERNAME!,
password: process.env.MATRIX_PASSWORD!,
userID: process.env.MATRIX_USER_ID,
},
});- Persistence behavior is active whenever Chat provides a
stateadapter. - Redis or another durable state adapter is recommended for restart durability.
deviceIDis inferred from auth when possible, then reused from state, and only generated as a last resort.recoveryKeyenables E2EE.inviteAutoJoin: {}enables invite auto-join.
createMatrixAdapter({
baseURL: process.env.MATRIX_BASE_URL!,
auth: {
type: "accessToken",
accessToken: process.env.MATRIX_ACCESS_TOKEN!,
userID: process.env.MATRIX_USER_ID,
},
recoveryKey: process.env.MATRIX_RECOVERY_KEY,
commandPrefix: "/",
roomAllowlist: ["!room:beeper.com"],
inviteAutoJoin: {
inviterAllowlist: ["@alice:beeper.com", "@ops:beeper.com"],
},
matrixSDKLogLevel: "error",
});Advanced tuning stays in code config:
createMatrixAdapter({
baseURL: process.env.MATRIX_BASE_URL!,
auth: {
type: "accessToken",
accessToken: process.env.MATRIX_ACCESS_TOKEN!,
userID: process.env.MATRIX_USER_ID,
},
e2ee: {
useIndexedDB: false,
cryptoDatabasePrefix: "beeper-matrix-bot",
},
persistence: {
keyPrefix: "my-bot",
session: {
ttlMs: 86_400_000,
},
sync: {
persistIntervalMs: 10_000,
},
},
});createMatrixAdapter() with no arguments uses only these env vars:
| Variable | Required | Description |
|---|---|---|
MATRIX_BASE_URL |
Yes | Matrix homeserver base URL |
MATRIX_ACCESS_TOKEN |
Yes* | Access token for access-token auth |
MATRIX_USERNAME |
Yes* | Username for password auth |
MATRIX_PASSWORD |
Yes* | Password for password auth |
MATRIX_USER_ID |
No | User ID hint |
MATRIX_DEVICE_ID |
No | Explicit device ID override |
MATRIX_RECOVERY_KEY |
No | Enables E2EE and key-backup bootstrap |
MATRIX_BOT_USERNAME |
No | Mention-detection username |
MATRIX_COMMAND_PREFIX |
No | Slash command prefix. Defaults to / |
MATRIX_INVITE_AUTOJOIN |
No | Enable invite auto-join |
MATRIX_INVITE_AUTOJOIN_ALLOWLIST |
No | Comma-separated Matrix user IDs allowed to invite the bot |
MATRIX_SDK_LOG_LEVEL |
No | Matrix SDK log level |
*Use either MATRIX_ACCESS_TOKEN, or MATRIX_USERNAME plus MATRIX_PASSWORD.
- A Matrix room is a Chat SDK channel.
- Top-level room messages belong to the channel timeline.
- Matrix threaded replies map to Chat SDK threads using
roomID + rootEventID. openDM(userId)reuses existing direct rooms when possible and creates one when needed.
| Feature | Supported |
|---|---|
| Mentions | Yes |
| Rich text | Yes, via Matrix formatted_body and m.mentions |
| Thread replies | Yes |
| Reactions (add/remove) | Yes |
| Message edits | Yes |
| Message deletes | Yes |
| Typing indicator | Yes |
| Direct messages | Yes |
| File uploads | Yes |
| Message history | Yes |
| Channel and thread metadata | Yes |
| E2EE | Yes |
| Invite auto-join | Yes |
| Slash commands | Prefix-parsed from message text |
| Webhooks | No, this adapter uses sync polling |
| Cards | No |
| Modals | No |
| Ephemeral messages | No |
| Native streaming | No |
For production, pair the adapter with a durable Chat state adapter such as Redis.
import { Chat } from "chat";
import { createRedisState } from "@chat-adapter/state-redis";
import { createMatrixAdapter } from "@beeper/chat-adapter-matrix";
const matrix = createMatrixAdapter({
baseURL: process.env.MATRIX_BASE_URL!,
auth: {
type: "accessToken",
accessToken: process.env.MATRIX_ACCESS_TOKEN!,
userID: process.env.MATRIX_USER_ID,
},
recoveryKey: process.env.MATRIX_RECOVERY_KEY,
});
const bot = new Chat({
userName: process.env.MATRIX_BOT_USERNAME ?? "beeper-bot",
state: createRedisState({ url: process.env.REDIS_URL! }),
adapters: { matrix },
});Persistence covers:
- generated or inferred device IDs
- password login sessions
- DM room mappings
- Matrix sync snapshots
- E2EE secrets bundles when E2EE is enabled
The adapter supports:
fetchMessage(threadId, messageId)fetchMessages(threadId, options)fetchChannelMessages(channelId, options)fetchThread(threadId)fetchChannelInfo(channelId)listThreads(channelId, options)openDM(userId)
handleWebhook()returns501by design because Matrix uses sync polling here.- Cards, modals, and ephemeral messages are not implemented.
- Native streaming is not implemented at the adapter layer.
- Slash commands are parsed from plain text messages; Matrix does not provide native slash command events.
examples/bot.tsuses in-memory state for local development.examples/bot.redis.tsuses Redis-backed state for restart durability.examples/.env.examplelists the supported env vars for the examples.scripts/get-access-token.tshelps generate Beeper credentials interactively.
For release-specific changes, see CHANGELOG.md.
MIT