# Messages Overview

Messages are the core building blocks of a chat application. This page covers sending, retrieving, updating, and deleting messages, as well as how Stream processes and formats message content.

## Sending a Message

To send a message to a channel, use the `sendMessage` method:

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val channelClient = client.channel("messaging", "general")
val message = Message(text = "Hello, world!")

channelClient.sendMessage(message).enqueue { result ->
  if (result is Result.Success) {
    val sentMessage: Message = result.value
  } else {
    // Handle Result.Failure
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
const message = await channel.sendMessage({
  text: "Hello, world!",
});
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
import StreamChat

let channelId = ChannelId(type: .messaging, id: "general")
let channelController = chatClient.channelController(for: channelId)

channelController.createNewMessage(text: "Hello, world!") { result in
  switch result {
  case .success(let messageId):
    print(messageId)
  case .failure(let error):
    print(error)
  }
}
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
final message = Message(text: "Hello, world!");
await channel.sendMessage(message);
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\ChatClient;
use GetStream\GeneratedModels as Models;

$response = $client->sendMessage("messaging", "general", new Models\SendMessageRequest(
    message: new Models\MessageRequest(
        text: "Hello, world!",
        userID: "user-id",
    ),
));
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import MessageRequest

channel.send_message(MessageRequest(text="Hello, world!", user_id=user_id))
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
resp, err := channel.SendMessage(ctx, &getstream.SendMessageRequest{
    Message: getstream.MessageRequest{
        Text:   getstream.PtrTo("Hello, world!"),
        UserID: getstream.PtrTo(userID),
    },
})
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
const FMessage Message{TEXT("Hello, world!")};
Channel->SendMessage(Message);
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
var resp = await chat.SendMessageAsync("messaging", channelId,
    new SendMessageRequest
    {
        Message = new MessageRequest
        {
            Text = "Hello, world!",
            UserID = userId
        }
    });
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.send_message('messaging', channel_id, Models::SendMessageRequest.new(
  message: Models::MessageRequest.new(
    text: 'Hello, world!',
    user_id: user_id
  )
))
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id");
var message = await channel.SendNewMessageAsync("Hello, world!");
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK
ChannelClient channelClient = client.channel("messaging", "general");
Message message = new Message();
message.setText("Hello, world!");

channelClient.sendMessage(message).enqueue(result -> {
  if (result.isSuccess()) {
    Message sentMessage = result.data();
  } else {
    // Handle result.error()
  }
});

// Backend SDK
chat.sendMessage(type, id, SendMessageRequest.builder()
    .message(MessageRequest.builder()
        .text("Hello, world!")
        .userID(userId)
        .build())
    .build()).execute();
```

</codetabs-item>

</codetabs>

<admonition type="info">

Server-side SDKs require a `user_id` parameter to specify who is sending the message. Client-side SDKs set this automatically based on the connected user.

</admonition>

### Message Parameters

| Name                  | Type   | Description                                                                                                              | Default | Optional |
| --------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------ | ------- | -------- |
| text                  | string | The message text. Supports markdown and automatic URL enrichment.                                                        |         | ✓        |
| attachments           | array  | A list of attachments (audio, video, image, text). Maximum 30 attachments per message with a combined size limit of 5KB. |         | ✓        |
| user_id               | object | Required for server-side SDKs. Set automatically in client-side mode.                                                    |         | ✓        |
| mentioned_users       | array  | A list of user IDs mentioned in the message. You receive back the full user data in the response. Maximum 25 users.      |         | ✓        |
| custom data           | object | Extra data for the message. Must not exceed 5KB in size.                                                                 |         | ✓        |
| skip_push             | bool   | If true, do not send a push notification for this message.                                                               | false   | ✓        |
| restricted_visibility | array  | Send the message only to specific channel members, identified by their user IDs.                                         |         | ✓        |

### Sending Messages with Attachments

Messages can include attachments such as images, videos, audio files, and custom content. The following example shows how to send a message with an image attachment and user mentions:

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val attachment = Attachment(
  type = "image",
  imageUrl = "https://bit.ly/2K74TaG",
  thumbUrl = "https://bit.ly/2Uumxti",
  extraData = mutableMapOf("myCustomField" to 123),
)

val message = Message(
  text = "@Josh Check out this image!",
  attachments = mutableListOf(attachment),
  mentionedUsersIds = mutableListOf("josh-id"),
  extraData = mutableMapOf("priority" to "high"),
)

channelClient.sendMessage(message).enqueue { /* ... */ }
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
const message = await channel.sendMessage(
  {
    text: "@Josh Check out this image!",
    attachments: [
      {
        type: "image",
        asset_url: "https://bit.ly/2K74TaG",
        thumb_url: "https://bit.ly/2Uumxti",
        myCustomField: 123,
      },
    ],
    mentioned_users: [josh.id],
    priority: "high",
  },
  { skip_push: true },
);
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let channelId = ChannelId(type: .messaging, id: "general")
let channelController = chatClient.channelController(for: channelId)

let fileURL = URL(filePath: "<file url>")
let attachment = try AnyAttachmentPayload(
  localFileURL: fileURL,
  attachmentType: .file
)

channelController.createNewMessage(
  text: "Hello",
  attachments: [attachment],
  mentionedUserIds: ["josh-id"],
  extraData: ["priority": .string("high")]) { result in
    // Handle result
  }
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
final message = Message(
  text: "@Josh Check out this image!",
  attachments: [
    Attachment(
      type: "image",
      assetUrl: "https://bit.ly/2K74TaG",
      thumbUrl: "https://bit.ly/2Uumxti",
      extraData: {"myCustomField": 123},
    ),
  ],
  mentionedUsers: [User(id: "josh")],
  extraData: {"priority": "high"},
);

await channel.sendMessage(message);
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\ChatClient;
use GetStream\GeneratedModels as Models;

$response = $client->sendMessage("messaging", "general", new Models\SendMessageRequest(
    message: new Models\MessageRequest(
        text: "@Josh Check out this image!",
        userID: "user-id",
        attachments: [
            new Models\Attachment(
                type: "image",
                assetUrl: "https://bit.ly/2K74TaG",
                thumbUrl: "https://bit.ly/2Uumxti",
                custom: (object)["myCustomField" => 123],
            ),
        ],
        mentionedUsers: ["josh-id"],
        custom: (object)["priority" => "high"],
    ),
    skipPush: true,
));
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import MessageRequest, Attachment

channel.send_message(
    MessageRequest(
        text="@Josh Check out this image!",
        attachments=[
            Attachment(
                type="image",
                asset_url="https://bit.ly/2K74TaG",
                thumb_url="https://bit.ly/2Uumxti",
                custom={"myCustomField": 123},
            )
        ],
        mentioned_users=["josh-id"],
        user_id=user_id,
        custom={"priority": "high"},
    ),
    skip_push=True,
)
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
resp, err := channel.SendMessage(ctx, &getstream.SendMessageRequest{
    Message: getstream.MessageRequest{
        Text:   getstream.PtrTo("@Josh Check out this image!"),
        UserID: getstream.PtrTo(userID),
        Attachments: []getstream.Attachment{
            {
                Type:     getstream.PtrTo("image"),
                ThumbUrl: getstream.PtrTo("https://bit.ly/2K74TaG"),
                AssetUrl: getstream.PtrTo("https://bit.ly/2Uumxti"),
                Custom:   map[string]any{"myCustomField": 123},
            },
        },
        MentionedUsers: []string{"josh-id"},
        Custom:         map[string]any{"priority": "high"},
    },
    SkipPush: getstream.PtrTo(true),
})
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.send_message('messaging', channel_id, Models::SendMessageRequest.new(
  message: Models::MessageRequest.new(
    text: '@Josh Check out this image!',
    user_id: user_id,
    attachments: [
      Models::Attachment.new(
        type: 'image',
        asset_url: 'https://bit.ly/2K74TaG',
        thumb_url: 'https://bit.ly/2Uumxti',
        custom: { 'myCustomField' => 123 }
      )
    ],
    mentioned_users: ['josh-id'],
    custom: { 'priority' => 'high' }
  ),
  skip_push: true
))
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
var resp = await chat.SendMessageAsync("messaging", channelId,
    new SendMessageRequest
    {
        Message = new MessageRequest
        {
            Text = "@Josh Check out this image!",
            UserID = userId,
            Attachments = new List<Attachment>
            {
                new Attachment
                {
                    Type = "image",
                    AssetUrl = "https://bit.ly/2K74TaG",
                    ThumbUrl = "https://bit.ly/2Uumxti",
                }
            },
            MentionedUsers = new List<string> { "josh-id" },
            Custom = new Dictionary<string, object> { ["priority"] = "high" }
        },
        SkipPush = true
    });
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK
Attachment attachment = new Attachment();
attachment.setType("image");
attachment.setImageUrl("https://bit.ly/2K74TaG");
attachment.setThumbUrl("https://bit.ly/2Uumxti");
attachment.getExtraData().put("myCustomField", 123);

Message message = new Message();
message.setText("@Josh Check out this image!");
message.getAttachments().add(attachment);
message.setMentionedUsersIds(Arrays.asList("josh-id"));
message.getExtraData().put("priority", "high");

channelClient.sendMessage(message).enqueue(result -> { /* ... */ });

// Backend SDK
chat.sendMessage(type, id, SendMessageRequest.builder()
    .message(MessageRequest.builder()
        .text("@Josh Check out this image!")
        .attachments(List.of(Attachment.builder()
            .type("image")
            .assetUrl("https://bit.ly/2K74TaG")
            .thumbUrl("https://bit.ly/2Uumxti")
            .custom(Map.of("myCustomField", 123))
            .build()))
        .mentionedUsers(List.of("josh-id"))
        .userID(userId)
        .custom(Map.of("priority", "high"))
        .build())
    .skipPush(true)
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");

var message = await channel.SendNewMessageAsync(new StreamSendMessageRequest
{
  Text = "Hello",
  MentionedUsers = new List<IStreamUser> { someUser },
  Pinned = true,
  PinExpires = new DateTimeOffset(DateTime.Now).AddDays(7),
  CustomData = new StreamCustomDataRequest
  {
    { "priority", "high" }
  }
});
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
FMessage Message{TEXT("@Josh Check out this image!")};
Message.ExtraData.SetString(TEXT("priority"), TEXT("high"));
Channel->SendMessage(Message);
// NOTE: the Unreal SDK does not currently support attachments or mentioned users
```

</codetabs-item>

</codetabs>

### Supported Attachment Types

Stream's UI components support the following attachment types by default:

- **Audio**: Audio files and recordings
- **Video**: Video files
- **Image**: Photos and images
- **Text**: Text-based attachments

You can define custom attachment types as long as you implement the frontend rendering logic to handle them. Common use cases include embedding products (with photos, descriptions, and links) or sharing user locations.

The [React tutorial](https://getstream.io/chat/react-chat/tutorial/) explains how to customize the `Attachment` component.

## Message Processing

When you send a message, Stream performs several processing steps:

1. **Markdown parsing**: The message text is parsed for markdown formatting.
2. **URL enrichment**: The first URL in the message text is scraped for Open Graph data, adding preview information automatically.
3. **Slash commands**: Commands like `/giphy`, `/ban`, and `/flag` are processed and executed.

### URL Enrichment

When a message contains a URL, Stream automatically scrapes the page for Open Graph metadata and creates an attachment with the preview information:

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
const response = await channel.sendMessage({
  text: "Check this out https://imgur.com/r/bears/4zmGbMN",
});
```

</codetabs-item>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val message = Message(
  text = "Check this out https://imgur.com/r/bears/4zmGbMN"
)
channelClient.sendMessage(message).enqueue { /* ... */ }
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import MessageRequest

channel.send_message(
    MessageRequest(text="Check this out https://imgur.com/r/bears/4zmGbMN", user_id=user_id)
)
```

</codetabs-item>

</codetabs>

The resulting message includes an automatically generated attachment:

```json
{
  "id": "message-id",
  "text": "Check this out https://imgur.com/r/bears/4zmGbMN",
  "attachments": [
    {
      "type": "image",
      "author_name": "Imgur",
      "title": "An update: Dushi made it safe to Bear Sanctuary",
      "title_link": "https://imgur.com/4zmGbMN",
      "text": "1678 views on Imgur",
      "image_url": "https://i.imgur.com/4zmGbMN.jpg?fb",
      "thumb_url": "https://i.imgur.com/4zmGbMN.jpg?fb",
      "og_scrape_url": "https://imgur.com/r/bears/4zmGbMN"
    }
  ]
}
```

### URL Attachment Fields

| Name          | Type   | Description                                                                 |
| ------------- | ------ | --------------------------------------------------------------------------- |
| type          | string | The attachment type based on the URL resource: `audio`, `image`, or `video` |
| author_name   | string | The name of the author                                                      |
| title         | string | The attachment title                                                        |
| title_link    | string | The link the attachment points to                                           |
| text          | string | The attachment description text                                             |
| image_url     | string | The URL to the attached image                                               |
| thumb_url     | string | The URL to the attachment thumbnail                                         |
| asset_url     | string | The URL to the audio, video, or image resource                              |
| og_scrape_url | string | The original URL that was scraped                                           |

<admonition type="info">

The Open Graph scraper uses this user agent: `getstream.io/opengraph-bot facebookexternalhit/1.1`. If you control the target website, ensure this user agent is not blocked for optimal results.

</admonition>

## Message Response Structure

The API returns a message object containing all information about the message, including the author, attachments, reactions, and metadata.

### Message Fields

| Field Name              | Description                                                                        |
| ----------------------- | ---------------------------------------------------------------------------------- |
| id                      | Unique message identifier. Maximum 255 characters; cannot contain `,` or `%`.      |
| text                    | The raw message text                                                               |
| html                    | Safe HTML generated from the text. Can only be set via server-side APIs or import. |
| type                    | Message type: `regular`, `ephemeral`, `error`, `reply`, `system`, or `deleted`     |
| cid                     | The channel ID in the format `type:id`                                             |
| user                    | The author user object                                                             |
| attachments             | List of attachments (maximum 30)                                                   |
| mentioned_users         | Users mentioned in the message                                                     |
| reaction_counts         | Reaction counts by type (deprecated, use `reaction_groups`)                        |
| reaction_scores         | Reaction scores by type                                                            |
| reaction_groups         | Reaction statistics grouped by type with count, scores, and timestamps             |
| latest_reactions        | The 10 most recent reactions                                                       |
| own_reactions           | Reactions added by the current user                                                |
| reply_count             | Number of replies to this message                                                  |
| thread_participants     | Users who have participated in the thread                                          |
| parent_id               | ID of the parent message if this is a reply                                        |
| quoted_message_id       | ID of a quoted message                                                             |
| pinned                  | Whether the message is pinned                                                      |
| pinned_at               | When the message was pinned                                                        |
| pinned_by               | User who pinned the message                                                        |
| pin_expires             | When the pin expires (null for no expiration)                                      |
| silent                  | Whether this is a silent message (no push notifications)                           |
| created_at              | When the message was created                                                       |
| updated_at              | When the message was last updated                                                  |
| deleted_at              | When the message was deleted                                                       |
| message_text_updated_at | When the message text was last updated                                             |

<disclosure label="Example Response">

```json
{
  "id": "msg-a8f3b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "text": "Hey @sarah-miller, the new design mockups are ready! Let me know what you think 🎨",
  "html": "",
  "type": "regular",
  "cid": "messaging:project-apollo",
  "user": {
    "id": "alex-chen",
    "name": "Alex Chen",
    "image": "https://cdn.example.com/avatars/alex-chen.jpg",
    "role": "user",
    "created_at": "2024-03-12T09:15:00.000Z",
    "updated_at": "2024-11-28T16:42:00.000Z"
  },
  "attachments": [
    {
      "type": "image",
      "image_url": "https://cdn.example.com/uploads/mockup-v2-homepage.png",
      "thumb_url": "https://cdn.example.com/uploads/thumbs/mockup-v2-homepage.png",
      "title": "Homepage Redesign v2",
      "fallback": "Homepage Redesign v2"
    },
    {
      "type": "image",
      "image_url": "https://cdn.example.com/uploads/mockup-v2-dashboard.png",
      "thumb_url": "https://cdn.example.com/uploads/thumbs/mockup-v2-dashboard.png",
      "title": "Dashboard Redesign v2",
      "fallback": "Dashboard Redesign v2"
    }
  ],
  "mentioned_users": [
    {
      "id": "sarah-miller",
      "name": "Sarah Miller",
      "image": "https://cdn.example.com/avatars/sarah-miller.jpg"
    }
  ],
  "reaction_counts": {
    "love": 3,
    "fire": 2,
    "thumbsup": 1
  },
  "reaction_scores": {
    "love": 3,
    "fire": 2,
    "thumbsup": 1
  },
  "reaction_groups": {
    "love": {
      "count": 3,
      "sum_scores": 3,
      "first_reaction_at": "2024-12-11T14:32:00.000Z",
      "last_reaction_at": "2024-12-11T15:18:00.000Z"
    },
    "fire": {
      "count": 2,
      "sum_scores": 2,
      "first_reaction_at": "2024-12-11T14:35:00.000Z",
      "last_reaction_at": "2024-12-11T14:52:00.000Z"
    },
    "thumbsup": {
      "count": 1,
      "sum_scores": 1,
      "first_reaction_at": "2024-12-11T16:05:00.000Z",
      "last_reaction_at": "2024-12-11T16:05:00.000Z"
    }
  },
  "latest_reactions": [
    {
      "type": "thumbsup",
      "user_id": "sarah-miller",
      "created_at": "2024-12-11T16:05:00.000Z"
    },
    {
      "type": "love",
      "user_id": "mike-johnson",
      "created_at": "2024-12-11T15:18:00.000Z"
    },
    {
      "type": "fire",
      "user_id": "emma-wilson",
      "created_at": "2024-12-11T14:52:00.000Z"
    }
  ],
  "own_reactions": [],
  "reply_count": 2,
  "deleted_reply_count": 0,
  "parent_id": "",
  "show_in_channel": false,
  "thread_participants": [
    {
      "id": "sarah-miller",
      "name": "Sarah Miller"
    },
    {
      "id": "alex-chen",
      "name": "Alex Chen"
    }
  ],
  "quoted_message_id": "",
  "quoted_message": null,
  "pinned": true,
  "pinned_at": "2024-12-11T17:00:00.000Z",
  "pinned_by": {
    "id": "sarah-miller",
    "name": "Sarah Miller"
  },
  "pin_expires": null,
  "silent": false,
  "shadowed": false,
  "i18n": {},
  "image_labels": {},
  "custom": {},
  "restricted_visibility": [],
  "poll_id": "",
  "poll": null,
  "created_at": "2024-12-11T14:30:00.000Z",
  "updated_at": "2024-12-11T17:00:00.000Z"
}
```

</disclosure>

### Message Types

| Type      | Description                                                                                                    |
| --------- | -------------------------------------------------------------------------------------------------------------- |
| regular   | A standard message posted to the channel. This is the default type.                                            |
| ephemeral | A temporary message delivered only to one user. Not stored in channel history. Used by commands like `/giphy`. |
| error     | An error message from a failed command. Ephemeral and only delivered to one user.                              |
| reply     | A message in a reply thread. Messages with a `parent_id` are automatically this type.                          |
| system    | A message generated by a system event, such as updating the channel or muting a user.                          |
| deleted   | A soft-deleted message.                                                                                        |

## Retrieving a Message

Use `getMessage` to retrieve a single message by its ID:

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
channelClient.getMessage("message-id").enqueue { result ->
  if (result is Result.Success) {
    val message: Message = result.value
  } else {
    // Handle Result.Failure
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
const message = await client.getMessage(messageID);

// For soft-deleted messages, retrieve the original content (server-side only)
const deletedMessage = await serverClient.getMessage(messageID, {
  show_deleted_message: true,
});
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let channelId = ChannelId(type: .messaging, id: "general")
let messageId = "message-id"
let messageController = chatClient.messageController(cid: channelId, messageId: messageId)

messageController.synchronize { error in
  print(error ?? messageController.message!)
}
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
final message = await client.getMessage("message-id");
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$response = $client->getMessage("message-id", false);
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
response = client.chat.get_message(id=msg_id)
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
resp, err := client.Chat().GetMessage(ctx, msgID, &getstream.GetMessageRequest{})
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

response = client.chat.get_message(msg_id)
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
var resp = await chat.GetMessageAsync(messageId);
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
Channel->GetMessage(TEXT("message-id"));
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK
channelClient.getMessage("message-id").enqueue(result -> {
  if (result.isSuccess()) {
    Message message = result.data();
  } else {
    // Handle result.error()
  }
});

// Backend SDK
chat.getMessage(messageId, GetMessageRequest.builder().build()).execute();
```

</codetabs-item>

</codetabs>

### Get Message Options

| Name                 | Type    | Description                                                     | Default | Optional |
| -------------------- | ------- | --------------------------------------------------------------- | ------- | -------- |
| show_deleted_message | boolean | If true, returns the original content of a soft-deleted message | false   | ✓        |

<admonition type="info">

The `show_deleted_message` option is only available for server-side calls.

</admonition>

## Updating a Message

To update a message, call `updateMessage` with a message object that includes the message ID:

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val updatedMessage = originalMessage.copy(text = "Updated message text")

channelClient.updateMessage(updatedMessage).enqueue { result ->
  if (result is Result.Success) {
    val message: Message = result.value
  } else {
    // Handle Result.Failure
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
const message = { id: messageId, text: "Updated message text" };
const updated = await client.updateMessage(message);
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
messageController.editMessage(text: "Updated message text") { error in
  print(error ?? messageController.message!)
}
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
await client.updateMessage(Message(id: "message-id", text: "Updated message text"));
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$client->updateMessage("message-id", new Models\UpdateMessageRequest(
    message: new Models\MessageRequest(
        text: "Updated message text",
        userID: "user-id",
    ),
));
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import MessageRequest

client.chat.update_message(
    id=msg_id,
    message=MessageRequest(text="Updated message text", user_id=user_id),
)
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
resp, err := client.Chat().UpdateMessage(ctx, msgID, &getstream.UpdateMessageRequest{
    Message: getstream.MessageRequest{
        Text:   getstream.PtrTo("Updated message text"),
        UserID: getstream.PtrTo(userID),
    },
})
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.update_message(msg_id, Models::UpdateMessageRequest.new(
  message: Models::MessageRequest.new(
    text: 'Updated message text',
    user_id: user_id
  )
))
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
await chat.UpdateMessageAsync(messageId, new UpdateMessageRequest
{
    Message = new MessageRequest
    {
        Text = "Updated message text",
        UserID = userId
    }
});
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
Message.Text = TEXT("Updated message text");
Channel->UpdateMessage(Message);
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
await message.UpdateAsync(new StreamUpdateMessageRequest
{
  Text = "Updated message text",
  CustomData = new StreamCustomDataRequest
  {
    {"tags", new [] {"edited"}}
  }
});
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK
message.setText("Updated message text");

channelClient.updateMessage(message).enqueue(result -> {
  if (result.isSuccess()) {
    Message updatedMessage = result.data();
  } else {
    // Handle result.error()
  }
});

// Backend SDK
chat.updateMessage(messageId, UpdateMessageRequest.builder()
    .message(MessageRequest.builder()
        .text("Updated message text")
        .userID(userId)
        .build())
    .build()).execute();
```

</codetabs-item>

</codetabs>

### Partial Update

Use partial updates to modify specific fields without replacing the entire message. This is useful when you want to retain existing custom data:

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// Update text
client.partialUpdateMessage(
  messageId = originalMessage.id,
  set = mapOf("text" to "Updated text"),
).enqueue { /* ... */ }

// Remove a custom field
client.partialUpdateMessage(
  messageId = originalMessage.id,
  unset = listOf("color"),
).enqueue { /* ... */ }

// Update nested properties
client.partialUpdateMessage(
  messageId = originalMessage.id,
  set = mapOf("details.status" to "complete"),
).enqueue { /* ... */ }
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// Update text
await client.partialUpdateMessage(originalMessage.id, {
  set: { text: "Updated text" },
});

// Remove a custom field
await client.partialUpdateMessage(originalMessage.id, {
  unset: ["color"],
});

// Update nested properties
await client.partialUpdateMessage(originalMessage.id, {
  set: { "details.status": "complete" },
});
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
// Set fields
$client->updateMessagePartial($messageId, new Models\UpdateMessagePartialRequest(
    set: (object)[
        "text" => "Updated text",
        "details.status" => "complete",
    ],
    userID: "user-id",
));

// Unset fields
$client->updateMessagePartial($messageId, new Models\UpdateMessagePartialRequest(
    unset: ["color"],
    userID: "user-id",
));
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# Set fields
client.chat.update_message_partial(
    id=msg_id,
    set={"text": "Updated text", "details.status": "complete"},
    user_id=user["id"],
)

# Unset fields
client.chat.update_message_partial(
    id=msg_id,
    unset=["color"],
    user_id=user["id"],
)
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// Set fields
resp, err := client.Chat().UpdateMessagePartial(ctx, msgID, &getstream.UpdateMessagePartialRequest{
    Set: map[string]any{
        "text":           "Updated text",
        "details.status": "complete",
    },
    UserID: getstream.PtrTo(userID),
})

// Unset fields
resp, err = client.Chat().UpdateMessagePartial(ctx, msgID, &getstream.UpdateMessagePartialRequest{
    Unset:  []string{"color"},
    UserID: getstream.PtrTo(userID),
})
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Set fields
client.chat.update_message_partial(msg_id, Models::UpdateMessagePartialRequest.new(
  set: { 'text' => 'Updated text', 'details.status' => 'complete' },
  user_id: user['id']
))

# Unset fields
client.chat.update_message_partial(msg_id, Models::UpdateMessagePartialRequest.new(
  unset: ['color'],
  user_id: user['id']
))
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// Set fields
await chat.UpdateMessagePartialAsync(messageId, new UpdateMessagePartialRequest
{
    Set = new Dictionary<string, object>
    {
        { "text", "Updated text" },
        { "details.status", "complete" },
    },
    UserID = userId
});

// Unset fields
await chat.UpdateMessagePartialAsync(messageId, new UpdateMessagePartialRequest
{
    Unset = new List<string> { "color" },
    UserID = userId
});
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Set fields
chat.updateMessagePartial(messageId, UpdateMessagePartialRequest.builder()
    .set(Map.of("text", "Updated text", "details.status", "complete"))
    .userID(userId)
    .build()).execute();

// Unset fields
chat.updateMessagePartial(messageId, UpdateMessagePartialRequest.builder()
    .unset(List.of("color"))
    .userID(userId)
    .build()).execute();
```

</codetabs-item>

</codetabs>

## Deleting a Message

Messages can be deleted in three ways:

- **Soft delete**: The message is marked as deleted but data is preserved. Can be undeleted.
- **Hard delete**: The message and all related data (reactions, replies) are permanently removed.
- **Delete for me**: The message is marked as deleted only for the current user. Other channel members are not affected.

<admonition type="warning">

Deleting a message does not delete its file attachments. See [deleting attachments](/chat/docs/<framework>/file_uploads/#deleting-files-and-images/) for more information.

</admonition>

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// Soft delete
chatClient.deleteMessage(messageId = "message-id").enqueue { /* ... */ }

// Hard delete
chatClient.deleteMessage(messageId = "message-id", hard = true).enqueue { /* ... */ }

// Delete for me
chatClient.deleteMessageForMe(messageId = "message-id").enqueue { /* ... */ }
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
// Soft delete
await client.deleteMessage(messageID);

// Hard delete
await client.deleteMessage(messageID, { hardDelete: true });

// Delete for me
await client.deleteMessage(messageID, { deleteForMe: true });
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
// Soft delete
messageController.deleteMessage { error in /* ... */ }

// Hard delete
messageController.deleteMessage(hard: true) { error in /* ... */ }

// Delete for me
messageController.deleteMessageForMe { error in /* ... */ }
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// Soft delete
await channel.deleteMessage(message);

// Hard delete
await channel.deleteMessage(message, hard: true);

// Delete for me
await channel.deleteMessageForMe(message);
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
// Soft delete
$client->deleteMessage("message-id", false, '', false);

// Hard delete
$client->deleteMessage("message-id", true, '', false);
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
# Soft delete
client.chat.delete_message(id=msg_id)

# Hard delete
client.chat.delete_message(id=msg_id, hard=True)
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
// Soft delete
resp, err := client.Chat().DeleteMessage(ctx, msgID, &getstream.DeleteMessageRequest{})

// Hard delete
resp, err = client.Chat().DeleteMessage(ctx, msgID, &getstream.DeleteMessageRequest{
    Hard: getstream.PtrTo(true),
})
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Soft delete
client.chat.delete_message(msg_id)

# Hard delete
client.chat.delete_message(msg_id, true)
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// Soft delete
await chat.DeleteMessageAsync(messageId);

// Hard delete
await chat.DeleteMessageAsync(messageId, new { hard = "true" });
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
Channel->DeleteMessage(Message);
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Soft delete
await message.SoftDeleteAsync();

// Hard delete
await message.HardDeleteAsync();
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK
channelClient.deleteMessage("message-id", false).enqueue(result -> {
  if (result.isSuccess()) {
    Message deletedMessage = result.data();
  } else {
    // Handle result.error()
  }
});

// Backend SDK - soft delete
chat.deleteMessage(messageId, DeleteMessageRequest.builder().build()).execute();

// Backend SDK - hard delete
chat.deleteMessage(messageId, DeleteMessageRequest.builder()
    .Hard(true)
    .build()).execute();
```

</codetabs-item>

</codetabs>

### Delete Type Comparison

| Behavior                      | Soft Delete | Hard Delete | Delete for Me |
| ----------------------------- | ----------- | ----------- | ------------- |
| Can be done client-side       | ✓           | ✓           | ✓             |
| Message type set to "deleted" | ✓           | -           | ✓             |
| Data preserved                | ✓           | -           | ✓ (for user)  |
| Reactions and replies kept    | ✓           | -           | ✓             |
| Can be undeleted              | ✓           | -           | -             |
| Affects other users           | ✓           | ✓           | -             |
| Recoverable                   | ✓           | -           | -             |

<admonition type="info">

Delete for me is limited to 100 messages per user per channel. Contact support to increase this limit.

</admonition>

### Undeleting a Message

Soft-deleted messages can be restored using a server-side call:

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
await client.undeleteMessage(messageID, userID);
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$client->undeleteMessage("message-id", new Models\UndeleteMessageRequest(
    undeletedBy: "user-id",
));
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
client.chat.undelete_message(id=msg_id, undeleted_by=user_id)
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.undelete_message(msg_id, Models::UndeleteMessageRequest.new(
  undeleted_by: user_id
))
```

</codetabs-item>

</codetabs>

Messages can be undeleted if:

- The message was soft-deleted (not hard-deleted)
- The channel has not been deleted
- It is not a reply to a deleted message (the parent must be undeleted first)
- The user performing the undelete is valid


---

This page was last updated at 2026-03-13T13:15:54.334Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/dotnet-csharp/send_message/](https://getstream.io/chat/docs/dotnet-csharp/send_message/).