Skip to main content
Your bot can change a message after it sends it, remove one, and pin or unpin it. It can also hear when any of that happens, including its own changes. This page covers msg.edit(), msg.delete(), msg.pin(), and msg.unpin(), plus the events that report each change.
All four methods reject with a plain Error on a direct message, unconditionally and regardless of permissions. Direct messages arrive on the same messageCreate event as server messages, so guard with if (msg.isDM || !msg.serverId) return; before you capture or act on a message. Only msg.reply() and msg.channel.send() work in a DM.

Edit a message the bot sent

msg.edit(newContent) replaces the text of a message and returns Promise<void>. The server allows only the message’s author to edit it, so a bot can edit its own messages and never another member’s. The bot receives its own messages on the firehose, so it can hold one and edit it later.
An edit is re-encrypted through the same lane as a fresh send, under the channel’s current key. If the channel’s key cycled between the original send and the edit, the edited text is stored under the newer key. Members who hold that key read it normally.

Delete a message

msg.delete() removes a message and returns Promise<void>. The server allows the author to delete their own message, or a member holding message_manage to delete another member’s. This is the method a moderation bot reaches for. Pre-gate with an advisory can() check so the bot skips a delete the server would only refuse.
The msg.isDM || !msg.serverId guard is what narrows msg.serverId from string | null to string, which is what can() requires. Skip it and TypeScript rejects the call, while plain JavaScript silently treats every message as unpermitted.

Pin and unpin

msg.pin() and msg.unpin() both return Promise<void>. Pinning is allowed for the message’s author, or for a member holding the channel’s pin permission. Both work on any server Message the bot holds, whether it came from the firehose or from fetchMessages.
Pinning is gated by a channel-level pin permission that is not one of the 31 named permissions (codes 0 to 30). Because it has no name in that vocabulary, a pin denial’s CloakActionError carries no .permission attribution, though .message still explains what happened.
Message.pinned is a boolean that is available when known, from history via fetchMessages or from a pinUpdate event. Treat undefined as unknown rather than as unpinned.

Every action needs createdAt

edit(), delete(), pin(), and unpin() all need the message’s createdAt, because the server locates the row by its own send time. Live messages from the firehose and messages loaded with fetchMessages both carry it, so you rarely have to think about this.
If createdAt is missing, the call rejects with a plain Error before it ever reaches the server. That is a separate check from the DM rule above: a DM message rejects even when createdAt is present.

Handle a denied action

All four methods reject with a CloakActionError when the server refuses. Catch it, read .message, and branch on .code when you need the specific reason.
Confirming a pin needs a Cloak server new enough to send the bot a success acknowledgement. Against an older backend the call cannot confirm and times out even though the pin landed. See What the server must support. See Handling errors for the full pattern.

React to changes

Three events report changes to existing messages. messageUpdate and messageDelete fire for the bot’s own edits and deletes too, so filter where you only care about other members.
{ messageId, content, serverId, channelId }
content is the decrypted new text, best-effort. When the key is missing it is an empty string, never raw ciphertext.
{ messageId, serverId, channelId }
Fires when a message is removed.
{ messageId, pinned, serverId, channelId }
pinned is a boolean: true when the message was pinned, false when it was unpinned. The acknowledgement the server sends back for the bot’s own pin() call is a separate reply and does not fire this event.
Because messageUpdate and messageDelete fire for the bot’s own changes as well, remember the message ids the bot just acted on and skip them if you only want other members’ changes.
On an older Cloak server, serverId and channelId on these events can fall back to the current selection. An up-to-date Cloak server always reports the message’s own location.

Next

Reactions

The other message action, under the same DM rule.

Message history

Load past messages with fetchMessages so you have one to act on.

Moderation

Kicks, bans, and timeouts alongside message removal.

Handling errors

What CloakActionError carries and how to catch it.