---
sidebar_position: 6
---

# 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

| Method | Capability |
| --- | --- |
| `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

```js
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

```js
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

```js
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

```js
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

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

Use moderation from explicit moderator actions, not automatic activation.

## Typing State

```js
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:

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

## Sync Messages

```js
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.