Updating and deleting messages

You can update and delete messages that have been published to a channel, for use cases such as:

  • Message editing - allow users to edit their messages in chat-like applications
  • Content moderation - remove or edit inappropriate content after publication
  • Gradual message building - a message can be published while still unfinished, and then repeatedly edited with more complete information, so that someone querying history once the message is complete will only see the final version

Updating or deleting a message does not modify any messages that have been received by subscribing clients in-place: a given Message object is immutable. Rather, it publishes a new message to the channel, with an action of message.update or message.delete, with the same serial as the original message, that subscribing clients can see and act on.

It also replaces the original message in message history, so history queries will see the latest version of the message (but in the place in history of the original).

You can specify metadata (such as the reason for the update and which client is doing the update), which is published along with it.

You can access the full version history of any given message.

Enable message updates and deletes

Message updates and deletes can be enabled for a channel or channel namespace with the Message annotations, updates, and deletes channel rule.

  1. Go to the Settings tab of an app in your dashboard.
  2. Under channel rules, click Add new rule.
  3. Enter the channel name or channel namespace on which to enable message updates and deletes.
  4. Check Message annotations, updates, and deletes to enable the feature.
  5. Click Create channel rule to save.

Update a message

To update an existing message, use the updateMessage() method on a REST or realtime channel. The published update will have an action of message.update.

The message is identified by its serial, which is populated by Ably. So to publish an update to a message, you have to have received it, as a subscriber or by querying history. Once you have that message, you can either take it, make your desired changes, and use that; or make a new Message object and just set its serial to that of the original message, as appropriate in your usecase.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

const realtime = new Ably.Realtime({ key: 'demokey:*****' });
// This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes
const channel = realtime.channels.get('updates:example');

// First subscribe to messages
await channel.subscribe((msg) => {
  if (msg.data === 'original-data') {
    // Publish an update

    // First way: mutate the received Message
    msg.data = 'new-data-1';
    await channel.updateMessage(msg, { description: 'reason for first update' });

    // Second way: publish a new Message using the serial
    const msg2 = {
      name: 'message-name',
      serial: msg.serial,
    };
    await channel.updateMessage(msg2, { description: 'reason for second update' });
  }
});

// Publish the original message
await channel.publish({
  name: 'message-name',
  data: 'original-data',
});
API key:
DEMO ONLY

Patch semantics

When updating a message, the fields you specify in the update will replace the corresponding fields in the existing message, and other fields will remain as they were. For example, if a message has { name: "greeting", data: "hello" }, and you update it with { data: "hi" }, the result will be { name: "greeting", data: "hi" }.

The fields that can be updated are:

  • data
  • name
  • extras

Capabilities

To update messages, clients need one of the following capabilities:

CapabilityDescription
message-update-ownCan update your own messages (more precisely, messages where the original publisher's clientId matches the updater's clientId, where both are identified).
message-update-anyCan update any message on the channel.

Operation metadata

When updating a message, you can optionally provide metadata about the update operation:

PropertyDescriptionType
clientIdThe client identifier of the user performing the update (automatically populated if the delete is done by an identified client).String
descriptionA description of why the update was made.String
metadataAdditional metadata about the update operation.Object

This metadata will end up in the message's version property. See Message version structure for what this looks like.

Delete a message

To delete a message, use the deleteMessage() method on a REST or realtime channel. This is very much a 'soft' delete: it's just an update, but with an action of message.delete instead of message.update. It's up to your application to interpret it.

The latest version of each message will be accessible from history, including if that latest version happens to be a delete.

As with updating, the message is identified by its serial, which is populated by Ably. So to delete a message, you have to have received it, as a subscriber or by querying history. Once you have that message, you can either take it, make your desired changes, and use that; or make a new Message object and just set its serial to that of the original message, as appropriate in your usecase.

Deleting a message marks it as deleted without removing it from the server. The full message history remains accessible through the message versions API.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

const realtime = new Ably.Realtime({ key: 'demokey:*****' });
// This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes
const channel = realtime.channels.get('updates:example');

// First subscribe to messages
await channel.subscribe((msg) => {
  if (msg.data === 'original-data') {
    // Publish a delete
    // First way: just use the received Message
    await channel.deleteMessage(msg, { description: 'reason for first delete' });

    // Second way: publish a new Message using the serial
    const msg2 = { serial: msg.serial };
    await channel.deleteMessage(msg2, { description: 'reason for second delete' });
  }
});

// Publish the original message
await channel.publish({
  name: 'message-name',
  data: 'original-data',
});
API key:
DEMO ONLY

Patch semantics

Deleting has the same semantics as updating, so only fields you specify in the update will replace the corresponding fields in the existing message.

That means that if you e.g. want the deleted message to have empty data (to prevent users looking at raw history results from the API from seeing what the data used to be), you must explicitly set to e.g. an empty object when publishing the delete. (And even then, all previous versions are accessible through the version history API).

As with update, the fields that can be updated are data, name, and extras.

Capabilities

To delete messages, clients need one of the following capabilities:

CapabilityDescription
message-delete-ownCan delete your own messages (more precisely, messages where the original publisher's clientId matches the deleter's clientId, where both are identified).
message-delete-anyCan delete any message on the channel.

Operation metadata

When deleting a message, you can optionally provide metadata:

PropertyDescriptionType
clientIdThe client identifier of the user performing the delete (automatically populated if the delete is done by an identified client).String
descriptionA description of why the delete was made.String
metadataAdditional metadata about the delete operation.Object

This metadata will end up in the message's version property. See Message version structure for what this looks like.

Get the latest version of a message

To retrieve the most recent version of a specific message, use the getMessage() method on a REST channel. You can pass either the message's serial identifier as a string, or a message object with a serial property.

This operation requires the history capability.

1

2

3

4

5

6

const rest = new Ably.Rest({ key: 'demokey:*****' });
const channel = rest.channels.get('updates:example');

// could also use msg.serial, useful if you want to retrieve a
// message for a serial you have stored or passed around
const message = await channel.getMessage(msg);
API key:
DEMO ONLY

Get message versions

To retrieve all historical versions of a message, use the getMessageVersions() method. This returns a paginated result containing all versions of the message, including the original and all subsequent updates or delete operations, ordered by version.

This operation requires the history capability.

1

2

3

4

5

const rest = new Ably.Rest({ key: 'demokey:*****' });
const channel = rest.channels.get('updates:example');

const page = await channel.getMessageVersions(msg);
console.log(`Found ${page.items.length} versions`);
API key:
DEMO ONLY

Message version structure

A published update or delete contains version metadata in the version property. The following shows the structure of a message after it has been updated:

JSON

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

{
  // The top-level serial is a permanent identifier of the message, and remains
  // the same for all updates and deletes of that message
  "serial": "01826232498871-001@abcdefghij:001",

  // The clientId of the user who published the original message
  "clientId": "user123",

  // The timestamp of that original publish
  "timestamp": 1718195879988,

  // Main payload fields
  "name": "greeting",
  "data": "hello world (edited)",

  // The action tells you if it's an original ("message.create"), update,
  // delete, or annotation summary
  "action": "message.update",

  "version": {
    // The serial of the current version. For an original (with an action of
    // "message.create"), this will be equal to the top-level serial. You can
    // use this to compare different versions to see which one is more recent
    "serial": "01826232512345-002@abcdefghij:002",

    // The clientId of the user who made the update
    "clientId": "user123",

    // The timestamp of this latest version
    "timestamp": 1718195912345,

    // Update metadata, supplied by the user who published the update
    "description": "Fixed typo",
    "metadata": {
      "reason": "correction"
    }
  }
}

Message actions

The action property on a message indicates the type of operation:

ActionDescription
message.createThe original message
message.updateAn update to an existing message
message.deleteA deletion of a message
metaA message originating from ably rather than being published by a user, such as inband occupancy events
message.summaryTM5

Version ordering

Both the message serial and version.serial are lexicographically sortable strings, providing a deterministic ordering of messages and their versions.

To determine which version of a message is newer, compare the version.serial values.

In the case of updates or deletes made by different users at similar times, both will be published on the channel, but the one that is assigned the lexicographically-highest version.serial will 'win', in the sense that retrieving channel history will eventually always return that version of the message.

Select...