val channelClient = client.channel("messaging", "general") val message = Message( text = "Josh, I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish." ) channelClient.sendMessage(message).enqueue { result -> if (result is Result.Success) { val sentMessage: Message = result.value } else { // Handle Result.Failure } }Messages Overview
Let’s dive right into it, the example below shows how to send a simple message using Stream:
const message = await channel.sendMessage({ text: "@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.", });import StreamChat /// 1: Create a `ChannelId` that represents the channel you want to send a message to. let channelId = ChannelId(type: .messaging, id: "general") /// 2: Use the `ChatClient` to create a `ChatChannelController` with the `ChannelId`. let channelController = chatClient.channelController(for: channelId) /// 3: Call `ChatChannelController.createNewMessage` to create the message. channelController.createNewMessage(text: "Hello") { result in switch result { case .success(let messageId): print(messageId) case .failure(let error): print(error) } }final message = Message( text: '@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.' ); await channel.sendMessage(message);// Sending a message from jenny mentioning bob-1 $message = $channel->sendMessage([ 'text' => '@Bob I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.', ], 'jenny');message = { "text": "@Bob I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.", } channel.send_message(message, user_id)message := &stream_chat.Message{ Text: "@Bob I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.", } filledMessage, err := channel.SendMessage(message, userID)const FMessage Message{TEXT("@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.")}; Channel->SendMessage(Message);await messageClient.SendMessageAsync(channel.Type, channel.Id, "jenny", "@Bob I told them I was pesca-pescatarian.");# require 'stream-chat' message = { "text" => "@Bob I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.", } channel.send_message(message, user_id)var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); var message = await channel.SendNewMessageAsync("Hello world!");// Android SDK ChannelClient channelClient = client.channel("messaging", "general"); Message message = new Message(); message.setText("Josh, I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish."); channelClient.sendMessage(message).enqueue(result -> { if (result.isSuccess()) { Message sentMessage = result.data(); } else { // Handle result.error() } }); // Backend SDK Message.send(type, id) .message( MessageRequestObject.builder() .text( "@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.") .userId(userId) .build()) .request();Note how server side SDKs require that you specify user_id to indicate who is sending the message. You can add custom fields to both the message and the attachments. There’s a 5KB limit for the custom fields. File uploads are uploaded to the CDN so don’t count towards this 5KB limit.
| name | type | description | default | optional |
|---|---|---|---|---|
| text | string | The text of the chat message (Stream chat supports markdown and automatically enriches URLs). | ✓ | |
| attachments | array | A list of attachments (audio, videos, images, and text). Max is 30 attachments per message. The total combined attachment size can’t exceed 5KB. | ||
| user_id | object | This value is automatically set in client-side mode. You only need to send this value when using the server-side APIs. | ✓ | |
| mentioned_users | array | A list of users mentioned in the message. You send this as a list of user IDs and receive back the full user data. | ||
| message custom data | object | Extra data for the message. Must not exceed 5KB in size. | ||
| skip_push | bool | Do not send a push notification. | false | ✓ |
| restricted_visibility | bool | Send the message only to particular channel members, represented with their user ids. | ✓ |
Complex Example
A more complex example for creating a message is shown below:
// Create an image attachment val attachment = Attachment( type = "image", imageUrl = "https://bit.ly/2K74TaG", thumbUrl = "https://bit.ly/2Uumxti", extraData = mutableMapOf("myCustomField" to 123), ) // Create a message with the attachment and a user mention val message = Message( text = "@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.", attachments = mutableListOf(attachment), mentionedUsersIds = mutableListOf("josh-id"), extraData = mutableMapOf("anotherCustomField" to 234), ) // Send the message to the channel channelClient.sendMessage(message).enqueue { /* ... */ }const message = await channel.sendMessage( { text: "@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.", attachments: [ { type: "image", asset_url: "https://bit.ly/2K74TaG", thumb_url: "https://bit.ly/2Uumxti", myCustomField: 123, }, ], mentioned_users: [josh.id], anotherCustomField: 234, }, { skip_push: true }, );// 1: Create a `ChannelId` that represents the channel you want to send a message to. let channelId = ChannelId(type: .messaging, id: "general") // 2: Use the `ChatClient` to create a `ChatChannelController` with the `ChannelId`. let channelController = chatClient.channelController(for: channelId) // 3: Call `ChatChannelController.createNewMessage` let fileURL = URL(filePath: "<file url>") let attachment = try AnyAttachmentPayload( localFileURL: fileURL, attachmentType: .file ) channelController.createNewMessage( text: "Hello", pinning: .noExpiration, isSilent: false, attachments: [attachment], mentionedUserIds: [], quotedMessageId: "quotedid", skipPush: false, skipEnrichUrl: false, restrictedVisibility: [], extraData: ["priority": .bool(true)]) { result in // … }final message = Message( text: '@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.', attachments: [ Attachment( type: "image", assetUrl: "https://bit.ly/2K74TaG", thumbUrl: "https://bit.ly/2Uumxti", extraData: { "myCustomField": 123, } ), ], mentionedUsers: [ User(id: "josh") ], extraData: { "anotherCustomField": 234, }, ); await channel.sendMessage(message);// Sending a message from jenny mentioning bob-1 $message = $channel->sendMessage([ 'text' => '@Bob I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.', 'attachments' => [ [ 'type' => 'image', 'asset_url' => 'https://bit.ly/2K74TaG', 'thumb_url' => 'https://bit.ly/2Uumxti', 'myCustomField' => 123 ] ], 'mentioned_users' => ['bob-1'], 'anotherCustomField' => 456 ], 'jenny');message = { "text": "@Bob I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.", "attachments": [ { "type": "image", "asset_url": "https://bit.ly/2K74TaG", "thumb_url": "https://bit.ly/2Uumxti", "myCustomField": 123, } ], "mentioned_users": ["bob-1"], "anotherCustomField": 456, } channel.send_message(message, user_id)message := &stream_chat.Message{ Text: "@Bob I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.", Attachments: []*stream_chat.Attachment{ &stream_chat.Attachment{ Type: "image", ThumbURL: "https://bit.ly/2K74TaG", AssetURL: "https://bit.ly/2Uumxti", ExtraData: map[string]interface{}{ "myCustomField": 123, }, }, }, ExtraData: map[string]interface{}{ "anotherCustomField": 456, }, MentionedUsers: []*stream_chat.User{ &stream_chat.User{Name: "bob-1"}, }, } filledMessage, err := channel.SendMessage(message, userID, MessageSkipPush) // You can provide multiple SendMessageOption options. In this case MessageSkipPush, will prevent sending a push notificationFMessage Message{TEXT("@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.")}; Message.ExtraData.SetNumber(TEXT("anotherCustomField"), 234); Channel->SendMessage(Message); // NOTE: the Unreal SDK does not currently support attachments or mentioned usersmessage = { "text" => "@Bob I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.", "attachments" => [ { "type" => "image", "asset_url" => "https://bit.ly/2K74TaG", "thumb_url" => "https://bit.ly/2Uumxti", "myCustomField" => 123, } ], "mentioned_users" => ["bob-1"], "anotherCustomField" => 456, } channel.send_message(message, user_id)await messageClient.SendMessageAsync(_channel.Type, _channel.Id, new MessageRequest { Text = "@Bob I told them I was pesca-pescatarian.", Attachments = new[] { new Attachment { Type = "image", AssetURL = "https://bit.ly/2K74TaG", ThumbURL = "https://bit.ly/2Uumxti", }, }, MentionedUsers = new[] { "bob-1" }, }, userId);// Android SDK // Create an image attachment Attachment attachment = new Attachment(); attachment.setType("image"); attachment.setImageUrl("https://bit.ly/2K74TaG"); attachment.setThumbUrl("https://bit.ly/2Uumxti"); attachment.getExtraData().put("myCustomField", 123); // Create a message with the attachment and a user mention Message message = new Message(); message.setText("@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish."); message.getAttachments().add(attachment); message.setMentionedUsersIds(Arrays.asList("josh-id")); message.getExtraData().put("anotherCustomField", 234); // Send the message to the channel channelClient.sendMessage(message).enqueue(result -> { /* ... */ }); // Backend SDK Message.send(type, id) .message( MessageRequestObject.builder() .text( "@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.") .attachment( AttachmentRequestObject.builder() .type("image") .assetURL("https://bit.ly/2K74TaG") .thumbURL("https://bit.ly/2Uumxti") .additionalField("myCustomField", 123) .build()) .mentionedUsers(Arrays.asList(josh.getId())) .additionalField("anotherCustomField", 234) .userId(userId) .build()) .skipPush(true) .request();var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id"); IStreamUser someUser = null; // Send simple message with text only var message3 = await channel.SendNewMessageAsync("Hello"); // Send simple message with text only var message2 = await channel.SendNewMessageAsync("Let's start a thread!"); var message = await channel.SendNewMessageAsync(new StreamSendMessageRequest { MentionedUsers = new List<IStreamUser> { someUser }, // Mention a user ParentId = message2.Id, // Write in thread PinExpires = new DateTimeOffset(DateTime.Now).AddDays(7), // Pin for 7 days Pinned = true, QuotedMessage = message3, ShowInChannel = true, Text = "Hello", CustomData = new StreamCustomDataRequest { { "my_lucky_numbers", new List<int> { 7, 13, 81 } } } });mentioned_users field must contain a maximum of 25 items.
By default Stream’s UI components support the following attachment types:
- Audio
- Video
- Image
- Text
You can specify different types as long as you implement the frontend rendering logic to handle them. Common use cases include:
- Embedding products (photos, descriptions, outbound links, etc.)
- Sharing of a users location
The React tutorial for Stream Chat explains how to customize the Attachment component.
Get a Message
You can get a single message by its ID using the getMessage call:
channelClient.getMessage("message-id").enqueue { result -> if (result is Result.Success) { val message: Message = result.value } else { // Handle Result.Failure } }await client.getMessage(messageID); //if a message was soft-deleted it returns the original message await serverClient.getMessage(messageID, { show_deleted_message: true });import StreamChat /// 1: Create a `ChannelId` that represents the channel you want to get a message from. let channelId = ChannelId(type: .messaging, id: "general") /// 1: Create a `MessageId` that represents the message you want to get. let messageId = "message-id" /// 2: Use the `ChatClient` to create a `ChatChannelController` with the `ChannelId`. let messageController = chatClient.messageController(cid: channelId, messageId: messageId) /// 3: Call `ChatChannelController.createNewMessage` to create the message. messageController.synchronize { error in // handle possible errors / access message print(error ?? messageController.message!) }final message = await client.getMessage("message-id");$message = $client->getMessage('message-id');response = client.get_message(msg_id)msg, err := client.GetMessage(msgID)Channel->GetMessage(TEXT("message-id"));response = client.get_message(msg_id)await messageClient.GetMessageAsync(message.Id);// Will be implemented soon, raise a GitHub issue if you need this feature https://github.com/GetStream/stream-chat-unity/issues/// Android SDK channelClient.getMessage("message-id").enqueue(result -> { if (result.isSuccess()) { Message message = result.data(); } else { // Handle result.error() } }); // Backend SDK Message.get(messageId).request();Get a Message Options
| name | type | description | default | optional |
|---|---|---|---|---|
| show_deleted_message | boolean | if true, returns the original message | false | ✓ |
show_deleted_message is exposed for server-side calls only.
Update a Message
You can edit a message by calling updateMessage and including a message with an ID – the ID field is required when editing a message:
// Update some field of the message val message = messageToUpdate.copy(text = "my updated text") // Send the message to the channel channelClient.updateMessage(message).enqueue { result -> if (result is Result.Success) { val updatedMessage: Message = result.value } else { // Handle Result.Failure } }const message = { id: 123, text: "the edited version of my text" }; const update = await client.updateMessage(message);messageController.editMessage(text: "Hello!!!") { error in // handle possible errors / access message print(error ?? messageController.message!) }await client.updateMessage(Message(id: "123", text: "the edited version of my text"));$message = [ 'user_id' => 'jenny', 'id' => 'message-id', 'text' => 'the edited version of my text' ]; $update = $client->updateMessage($message);client.update_message( { "id": msg_id, "text": "the edited version of my text", "user_id": user_id, } )updatedMessage := &stream_chat.Message{ ID: msgID, Text: "the edited version of my text", User: &stream_chat.User{ID: userID}, } filledMessage, err := client.UpdateMessage(updatedMessage, msgID)// Update some field of the message Message.Text = TEXT("my updated text"); // Send the message to the channel Channel->UpdateMessage(Message);client.update_message( { "id" => msg_id, "text" => "the edited version of my text", "user_id" => user_id, } )var updated = await messageClient.UpdateMessageAsync(new MessageRequest { Id = message.Id, Text = "the edited version of my text", UserId = user.Id, });var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, channelId: "my-channel-id"); var message = await channel.SendNewMessageAsync("Hello world!"); // Edit message text and some custom data await message.UpdateAsync(new StreamUpdateMessageRequest { Text = "Hi everyone!", CustomData = new StreamCustomDataRequest { {"tags", new [] {"Funny", "Unique"}} } });// Android SDK // Update some field of the message message.setText("my updated text"); // Send the message to the channel channelClient.updateMessage(message).enqueue(result -> { if (result.isSuccess()) { Message updatedMessage = result.data(); } else { // Handle result.error() } }); // Backend SDK Message message = Message.get("123").request().getMessage(); MessageRequestObject messageRequestObject = MessageRequestObject.buildFrom(message); messageRequestObject.setText("the edited version of my text"); Message.update(message.getId()).message(messageRequestObject).request();Partial Update
A partial update can be used to set and unset specific fields when it is necessary to retain additional data fields on the object. AKA a patch style update.
val originalMessage = channelClient.sendMessage( message = Message( text = "this message is about to be partially updated", extraData = mapOf( "color" to "red", "details" to mapOf("status" to "pending") ) ) ).await().getOrThrow() // get the message object to obtain the auto-generated ID // partial update message text val text = "the text was partial updated" client.partialUpdateMessage( messageId = originalMessage.id, set = mapOf("text" to text), ).enqueue { /* ... */ } // unset color property client.partialUpdateMessage( messageId = originalMessage.id, unset = listOf("color"), ).enqueue { /* ... */ } // set nested property client.partialUpdateMessage( messageId = originalMessage.id, set = mapOf("details.status" to "complete"), ).enqueue { /* ... */ }let originalMessage = ( await channel.sendMessage({ text: "this message is about to be partially updated", color: "red", details: { status: "pending", }, }) ).message; // partial update message text const text = "the text was partial updated"; const updated = await client.partialUpdateMessage(originalMessage.id, { set: { text, }, }); // unset color property const updated = await client.partialUpdateMessage(originalMessage.id, { unset: ["color"], }); // set nested property const updated = await client.partialUpdateMessage(originalMessage.id, { set: { "details.status": "complete", }, });Message.partialUpdate(message.getId()) .setValue("text", "this message just got partially updated") .setValue("color", "red") .setValue("details.status", "pending") // nested object .user(userRequestObject) .request(); Message.partialUpdate(message.getId()) .unsetValue("color") .user(testUserRequestObject) .request();$client->partialUpdateMessage($messageId, [ "set" => [ "text" => "this message just got partially updated", "color" => "red", "details.status" => "pending" // nested object ], ]); $client->partialUpdateMessage($messageId, [ "unset" => ["details.status"], // remove nested object ]);client.update_message_partial( msg_id, { "set": { "text": "this message just got partially updated", "color": "red", "details.status": "pending", # nested object } }, user["id"], ) client.update_message_partial( msg_id, { "unset": ["details.status"], # remove nested object }, user["id"], )client.PartialUpdateMessage(ctx, msgId, &MessagePartialUpdateRequest{ PartialUpdate: PartialUpdate{ Set: map[string]interface{}{ "text": "this message just got partially updated", "color": "red", "details.status": "pending", // nested object }, }, }) client.PartialUpdateMessage(ctx, msgId, &MessagePartialUpdateRequest{ PartialUpdate: PartialUpdate{ Unset: []string{"details.status"}, // remove nested object }, })client.update_message_partial( msg_id, { "set" => { "text => "this message just got partially updated", "color" => "red", "details.status" => "pending", # nested object } }, user["id"], ) client.update_message_partial( msg_id, { "unset" => ["details.status"], # remove nested object }, user["id"], )await messageClient.UpdateMessagePartialAsync(message.Id, new MessagePartialUpdateRequest { UserId = user.Id, Set = new Dictionary<string, object> { { "text", "this message just got partially updated" }, { "color", "red" }, { "details.status", "pending" }, // nested object }, }); await messageClient.UpdateMessagePartialAsync(message.Id, new MessagePartialUpdateRequest { UserId = user.Id, Unset = new[] { "details.status" }, // remove nested object });// Will be implemented soon, raise a GitHub issue if you need this feature https://github.com/GetStream/stream-chat-unity/issues/Delete A Message
You can delete a message by calling deleteMessage and including a message with an ID. Messages can be soft deleted, hard deleted, or deleted for the current user only (delete for me). Unless specified via the hard or delete_for_me parameter, messages are soft deleted. Be aware that deleting a message doesn’t delete its attachments. See the docs for more information on deleting attachments.
Hard delete and “delete for me” are mutually exclusive options.
// 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 { /* ... */ }await client.deleteMessage(messageID); // hard delete the message await client.deleteMessage(messageID, true); // or hard delete the message with options object await client.deleteMessage(messageID, { hardDelete: true }); // delete for me supported only with options object await client.deleteMessage(messageID, { deleteForMe: true });// soft delete the message messageController.deleteMessage { error in // handle possible errors } // hard delete the message messageController.deleteMessage(hard: true) { error in // handle possible errors } // delete only for me messageController.deleteMessageForMe { error in // handle possible errors }// soft delete await channel.deleteMessage(message); // hard delete await channel.deleteMessage(message, hard: true); // delete for me await channel.deleteMessageForMe(message);// soft delete the message $client->deleteMessage('message-id'); // hard delete the message $client->deleteMessage('message-id', ['hard' => true]);# soft delete a message client.delete_message(msg_id) # hard delete a message client.delete_message(msg_id, hard=True)// Soft Delete err := client.DeleteMessage(msgID)Channel->DeleteMessage(Message);# soft delete a message client.delete_message(msg_id) # hard delete a message client.delete_message(msg_id, hard: True)// soft delete a message await messageClient.DeleteMessageAsync(message.Id); // hard delete a message await messageClient.DeleteMessageAsync(message.Id, hardDelete: true);// Soft delete await message.SoftDeleteAsync(); // Hard delete await message.HardDeleteAsync();// Android SDK channelClient.deleteMessage("message-id", false).enqueue(result -> { if (result.isSuccess()) { Message deletedMessage = result.data(); } else { // Handle result.error() } }); // Backend SDK Message.delete(messageId) .hard(true) // Optionally do a hard-delete .request();
Soft delete
Can be done client-side by users
Message is still returned in the message list and all its data is kept as it is
Message type is set to “deleted”
Reactions and replies are kept in place
Can be undeleted
Hard delete
Can be done client-side by users but be cautious this action is not recoverable
The message is removed from the channel and its data is wiped
All reactions are deleted
All replies and their reactions are deleted
Delete for me
Can be done client-side by users
Users can delete any message for themselves, regardless of who sent it
The message is marked as deleted only for the current user; other channel members are not affected
Message type is set to “deleted” with
deleted_for_meflag set totrueCannot be combined with hard delete
Limited to 100 messages per user per channel (contact support to increase this limit)
By default messages are soft deleted, this is a great way to keep the channel history consistent.
Undelete a message
A message that was soft-deleted can be undeleted. This is only allowed for server-side clients. The userID specifies the user that undeleted the message, which can be used for auditing purposes.
Messages can be undeleted if:
The message was soft-deleted
The channel has not been deleted
It is not a reply to a deleted message. If it is, the parent must be undeleted first
The user that undeletes the message is valid
await client.undeleteMessage(messageID, userID);$client->undeleteMessage('message-id', 'user-id');client.undelete_message(msg_id, user_id)client.undelete_message(msg_id, user_id)