Skip to main content

Messages and Typing API

The messages API reads current messages, sends messages, edits or deletes plugin-owned messages, moderates messages, syncs messages, and exposes typing state.

Required Capabilities

MethodCapability
messages.readCurrent()messages.read
messages.send(content, channelId?)messages.send
messages.sendAsPluginUser(request)messages.send
messages.setTyping(isTyping, channelId?)messages.send
messages.subscribeTyping(handler)messages.read
messages.edit(messageId, content)messages.editOwn
messages.delete(messageId)messages.deleteOwn
messages.moderateDelete(messageId)messages.moderate
messages.sync(messages)messages.sync

Read Current Messages

export function activate(context) {
const messages = context.api.messages.readCurrent();

context.api.logger.info('Current messages', messages.slice(-3).map((message) => ({
id: message.id,
channelId: message.channelId,
senderName: message.senderName,
content: message.content
})));
}

Send a Message

export function activate(context) {
const created = context.api.messages.send(
'Reminder: raid starts at 20:00. Bring repairs and snacks.',
'general'
);

context.api.logger.info('Sent reminder', { messageId: created.id });
}

Send as a Plugin User

export function activate(context) {
const botUserId = context.api.server.registerPluginUser({
id: 'poll-bot',
displayName: 'Poll Bot'
});

context.api.messages.sendAsPluginUser({
pluginUserId: botUserId,
channelId: 'general',
content: 'Poll is open: react with 1 for dungeon, 2 for arena, 3 for crafting.'
});
}

Capabilities required: users.manage and messages.send.

Edit and Delete Plugin-Owned Messages

export function activate(context) {
const message = context.api.messages.send('Draft event reminder', 'announcements');

context.api.messages.edit(message.id, 'Event reminder: voice meetup starts in 15 minutes.');
context.api.messages.delete(message.id);
}

Moderation Delete

export function activate(context) {
context.api.messages.moderateDelete('msg-spam-20260429-001');
}

Use moderation from explicit moderator actions, not automatic activation.

Typing State

export function activate(context) {
context.api.messages.setTyping(true, 'general');

setTimeout(() => {
context.api.messages.setTyping(false, 'general');
}, 1500);

context.subscriptions.push(context.api.messages.subscribeTyping((event) => {
context.api.logger.info('Typing event', {
displayName: event.displayName,
isTyping: event.isTyping,
channelId: event.channelId,
serverId: event.serverId,
voiceChannel: event.voiceChannel?.name ?? null
});
}));
}

Example typing event:

{
"serverId": "room-7ebdde75",
"channelId": "general",
"userId": "user-muse-01",
"displayName": "Muse",
"isTyping": true
}

Sync Messages

export function activate(context) {
context.api.messages.sync([
{
id: 'external-standup-001',
roomId: 'room-7ebdde75',
channelId: 'standup',
senderId: 'standup-importer',
senderName: 'Standup Importer',
content: 'Imported note: Alice is working on plugin docs.',
timestamp: 1777473600000,
isDeleted: false
}
]);
}

Sync should preserve message ids and timestamps from the source system when possible.