const draft = await channel.createDraft({ text: "this is a draft message", }); // Update the draft const draft = await channel.createDraft({ text: "this is an updated draft message", }); // Create a draft on a thread const draft = await channel.createDraft({ text: "this is a draft message", parent_id: parentMessageId, });
Draft Messages
Draft messages allow users to save messages as drafts for later use. This feature is useful when users want to compose a message but aren’t ready to send it yet.
Creating a draft message
It is possible to create a draft message for a channel or a thread. Only one draft per channel/thread can exist at a time, so a newly created draft overrides the existing one.
// Create/update a draft message in a channel let channelId = ChannelId(type: .messaging, id: "general") let channelController = chatClient.channelController(for: channelId) channelController.updateDraftMessage( text: "Hello, this is my draft message", isSilent: false, attachments: [imageAttachment], mentionedUserIds: ["user-id-1", "user-id-2"], quotedMessageId: "quoted-message-id", extraData: ["custom_field": .string("value")] ) { _ in print("Draft message saved: \(channelController.channel.draftMessage)") } // Create/update a draft message in a thread (parent message) let messageController = chatClient.messageController( cid: ChannelId(type: .messaging, id: "general"), messageId: "parent-message-id" ) messageController.updateDraftReply( text: "This is my draft reply", isSilent: false, attachments: [imageAttachment], mentionedUserIds: ["user-id-1"], quotedMessageId: "quoted-message-id", showReplyInChannel: true, extraData: ["custom_field": .string("value")] ) { _ in print("Draft message saved: \(messageController.message.draftReply)") }
// Create/update a draft message in a channel client.createDraftMessage( channelType = "messaging", channelId = "general", message = DraftMessage(text = "this is a draft message") ).enqueue { /* ... */ } // Create/update a draft message in a thread (parent message) client.createDraftMessage( channelType = "messaging", channelId = "general", message = DraftMessage( text = "this is a draft message", parentId = parentMessageId, ) ).enqueue { /* ... */ }
// Create/update a draft message in a channel resp, err := channel.CreateDraft(ctx, user.ID, &messageRequestMessage{ Text: "This is a draft message", }) // Create/update a draft message in a thread (parent message) resp, err := channel.CreateDraft(ctx, user.ID, &messageRequestMessage{ Text: "This is a draft message", ParentID: parentID, })
# Create/update a draft message in a channel response = await channel.create_draft({"text": "This is a draft message"}, user_id) # Create/update a draft message in a thread (parent message) response = await channel.create_draft( {"text": "This is a draft message", "parent_id": parent_id}, user_id )
// Create/update a draft message in a channel Draft.CreateDraftResponse response = Draft.createDraft(channelType, channelId) .message(MessageRequestObject.builder() .text("This is a draft message") .userId(userId) .build()) .userId(userId) .request(); // Create/update a draft message in a thread (parent message) Draft.CreateDraftResponse threadResponse = Draft.createDraft(channelType, channelId) .message(MessageRequestObject.builder() .text("This is a draft message") .parentId(parentMessageId) .userId(userId) .build()) .userId(userId) .request();
# Create/update a draft message in a channel draft_message = { text: 'This is a draft message' } response = channel.create_draft(draft_message, user_id) # Create/update a draft message in a thread (parent message) draft_reply = { text: 'This is a draft reply', parent_id: parent_message_id } response = channel.create_draft(draft_reply, user_id)
// Create/update a draft message in a channel $message = ["text" => "This is a draft message"]; $response = $channel->createDraft($message, $userId); // Create/update a draft message in a thread (parent message) $draftReply = ["text" => "This is a draft reply", "parent_id" => $parentMessageId]; $response = $channel->createDraft($draftReply, $userId);
// Create/update a draft message in a channel final draftMessage = DraftMessage( text: 'This is a draft message', ); final response = await channel.createDraft(draftMessage); // Create/update a draft message in a thread (parent message) final threadDraftMessage = DraftMessage( text: 'This is a draft message', parentId: parentMessageId, ); final response = await channel.createDraft(threadDraftMessage);
Deleting a draft message
You can delete a draft message for a channel or a thread as well.
// Channel draft await channel.deleteDraft(); // Thread draft await channel.deleteDraft({ parent_id: parentMessageId });
// Delete the draft message for a channel let channelId = ChannelId(type: .messaging, id: "general") let channelController = chatClient.channelController(for: channelId) channelController.deleteDraftMessage() // Delete the draft message for a thread let messageController = chatClient.messageController( cid: ChannelId(type: .messaging, id: "general"), messageId: "parent-message-id" ) messageController.deleteDraftReply()
// Channel draft client.deleteDraftMessage( channelType = "messaging", channelId = "general", message = DraftMessage() ).enqueue { /* ... */ } // Thread draft client.deleteDraftMessage( channelType = "messaging", channelId = "general", message = DraftMessage(parentId = parentMessageId) ).enqueue { /* ... */ }
// Channel draft resp, err := channel.DeleteDraft(ctx, user.ID, nil) // Thread draft resp, err := channel.DeleteDraft(ctx, user.ID, parentMessageID)
# Channel draft await channel.delete_draft(user_id) # Thread draft await channel.delete_draft(user_id, parent_id=parent_id)
// Delete the draft message for a channel Draft.deleteDraft(channelType, channelId) .userId(userId) .request(); // Delete the draft message for a thread Draft.deleteDraft(channelType, channelId) .userId(userId) .parentId(parentMessageId) .request();
# Delete the draft message for a channel channel.delete_draft(user_id) # Delete the draft message for a thread channel.delete_draft(user_id, parent_id: parent_message_id)
// Delete the draft message for a channel $channel->deleteDraft($userId); // Delete the draft message for a thread $channel->deleteDraft($userId, $parentMessageId);
// Delete the draft message for a channel await channel.deleteDraft(); // Delete the draft message for a thread await channel.deleteDraft(parentId: parentMessageId);
Loading a draft message
It is also possible to load a draft message for a channel or a thread. Although, when querying channels, each channel will contain the draft message payload, in case there is one. The same for threads (parent messages). So, for the most part this function will not be needed.
// Channel draft const draft = await channel.getDraft(); // Thread draft const threadDraft = await channel.getDraft({ parent_id: parentMessageId });
// Load the draft message for a channel let channelId = ChannelId(type: .messaging, id: "general") let channelController = chatClient.channelController(for: channelId) channelController.loadDraftMessage { result in switch result { case .success(let draftMessage): print("Draft message loaded: \(draftMessage)") case .failure(let error): print("Failed to load draft message: \(error)") } } // Load the draft message for a thread let messageController = chatClient.messageController( cid: ChannelId(type: .messaging, id: "general"), messageId: "parent-message-id" ) messageController.loadDraftReply { result in switch result { case .success(let draftReply): print("Draft reply loaded: \(draftReply)") case .failure(let error): print("Failed to load draft reply: \(error)") } }
// Channel draft resp, err := channel.GetDraft(ctx, nil, user.ID) // Thread draft resp, err := channel.GetDraft(ctx, parentMessageID, user.ID)
# Channel draft response = await channel.get_draft(user_id) # Thread draft response = await channel.get_draft(user_id, parent_id=parent_id)
// Load the draft message for a channel Draft.GetDraftResponse draftResponse = Draft.getDraft(channelType, channelId) .userId(userId) .request(); // Load the draft message for a thread Draft.GetDraftResponse threadDraftResponse = Draft.getDraft(channelType, channelId) .userId(userId) .parentId(parentMessageId) .request();
# Load the draft message for a channel response = channel.get_draft(user_id) # Load the draft message for a thread response = channel.get_draft(user_id, parent_id: parent_message_id)
// Load the draft message for a channel $response = $channel->getDraft($userId); // Load the draft message for a thread $response = $channel->getDraft($userId, $parentMessageId);
// Load the draft message for a channel final response = await channel.getDraft(); // Load the draft message for a thread final response = await channel.getDraft(parentId: parentMessageId);
Querying draft messages
The Stream Chat SDK provides a way to fetch all the draft messages for the current user. This can be useful to for the current user to manage all the drafts they have in one place.
// Query all user drafts const response = await client.queryDrafts({}); // Query drafts for certain channels and sort const response = await client.queryDrafts({ filter: { channel_cid: { $in: ["messaging:channel-1", "messaging:channel-2"] }, }, sort: [{ field: "created_at", direction: -1 }], });
// Load the draft messages for the current user let currentUserController = chatClient.currentUserController() currentUserController.loadDraftMessages { result in switch result { case .success: print("Draft messages loaded: \(currentUserController.draftMessages)") case .failure(let error): print("Failed to load draft messages: \(error)") } } // Whenever the drafts are updated, it will be notified through the currentUserController delegate class MyView: UIView, CurrentChatUserControllerDelegate { let controller: CurrentChatUserController init(controller: CurrentChatUserController) { self.controller = controller super.init(frame: .zero) controller.delegate = self } func currentUserController( _ controller: CurrentChatUserController, didChangeDraftMessages draftMessages: [DraftMessage] ) { // Handle the changes } }
// Query all user drafts client.queryDrafts( filter = Filters.neutral(), limit = 25, ).enqueue { /* ... */ } // Query drafts for certain channels and sort client.queryDrafts( filter = Filters.`in`("channel_cid", listOf("messaging:channel-1", "messaging:channel-2")), limit = 25, sort = QuerySortByField.descByName("created_at"), ).enqueue { /* ... */ }
// Query all user drafts resp, err := c.QueryDrafts(ctx, &QueryDraftsOptions{UserID: user.ID, Limit: 10}) // Query drafts for certain channels and sort resp, err := client.QueryDrafts(ctx, &QueryDraftsRequest{ UserID: user.ID, Filter: map[string]interface{}{ "channel_cid": map[string][]string{ "$in": {"messaging:channel-1", "messaging:channel-2"}, }, }, Sort: []*SortOption{ {Field: "created_at", Direction: 1}, }, })
# Query all user drafts response = await client.query_drafts(user_id=user_id, limit=10) # Query drafts for certain channels and sort response = await client.query_drafts( user_id, filter={ "channel_cid": {"$in": ["messaging:channel-1", "messaging:channel-2"]}, }, sort=[{"field": "created_at", "direction": SortOrder.ASC}], )
// Query all user drafts Draft.QueryDraftsResponse queryResponse = Draft.queryDrafts() .userId(userId) .limit(10) .request(); // Query drafts for certain channels and sort Draft.QueryDraftsResponse filteredResponse = Draft.queryDrafts() .userId(userId) .filter(FilterCondition.in("channel_cid", "messaging:channel-1", "messaging:channel-2")) .sort(Sort.builder().field("created_at").direction(Sort.Direction.DESC).build()) .request();
# Query all user drafts response = client.query_drafts(user_id) # Query drafts for certain channels and sort response = client.query_drafts( user_id, filter: { channel_cid: { '$in' => ['messaging:channel-1', 'messaging:channel-2'] } }, sort: [{ field: 'created_at', direction: -1 }] )
// Query all user drafts $response = $client->queryDrafts($userId); // Query drafts for certain channels and sort $response = $client->queryDrafts( $userId, ["channel_cid" => $channel->getCID()], [["field" => "created_at", "direction" => 1]] ); // Query drafts with pagination $response = $client->queryDrafts( $userId, options: ["limit" => 1] ); // Query drafts with pagination and next $response = $client->queryDrafts( $userId, options: ["limit" => 1, "next" => $response["next"]] );
// Query all user drafts final response = await client.queryDrafts(); // Query drafts for certain channels and sort final response = await client.queryDrafts( filter: Filter.in_( 'channel_cid', const ['messaging:channel-1', 'messaging:channel-2'], ), sort: const [SortOption.desc(DraftSortKey.createdAt)], );
Filtering is possible on the following fields:
Name | Type | Description | Supported operations | Example |
---|---|---|---|---|
channel_cid | string | the ID of the message | $in, $eq | { channel_cid: { $in: [ ‘channel-1’, ‘channel-2’ ] } } |
parent_id | string | the ID of the parent message | $in, $eq, $exists | { parent_id: ‘parent-message-id’ } |
created_at | string (RFC3339 timestamp) | the time the draft was created | $eq, $gt, $lt, $gte, $lte | { created_at: { $gt: ‘2024-04-24T15:50:00.00Z’ } |
Sorting is possible on the created_at
field. By default, draft messages are returned with the newest first.
Pagination
In case the user has a lot of draft messages, you can paginate the results.
// Query drafts with a limit const firstPage = await client.queryDrafts({ limit: 5, }); // Query the next page const secondPage = await client.queryDrafts({ limit: 5, next: firstPage.next, });
// Load the next page of draft messages currentUserController.loadMoreDraftMessages() // With a custom page size currentUserController.loadMoreDraftMessages(limit: 20) { result in switch result { case .success: print("Draft messages loaded: \(currentUserController.draftMessages)") case .failure(let error): print("Failed to load draft messages: \(error)") } }
// Query drafts with a limit val firstPage = client.queryDrafts( filter = filter, limit = 5, ).await().getOrThrow() // Query the next page val secondPage = client.queryDrafts( filter = filter, limit = 5, next = firstPage.next ).await().getOrThrow()
// Query drafts with a limit resp, err := client.QueryDrafts(ctx, &QueryDraftsOptions{UserID: user.ID, Limit: 5}) // Query the next page resp, err = client.QueryDrafts(ctx, &QueryDraftsOptions{ UserID: user.ID, Limit: 5, Next: *resp.Next, })
# Query drafts with a limit first_page = await client.query_drafts(user_id=user_id, options={"limit": 5}) # Query the next page second_page = await client.query_drafts( user_id=user_id, options={"limit": 5, "next": first_page["next"]} )
// Query drafts with a limit Draft.QueryDraftsResponse firstPage = Draft.queryDrafts() .userId(userId) .limit(5) .request(); // Query the next page Draft.QueryDraftsResponse secondPage = Draft.queryDrafts() .userId(userId) .limit(5) .next(firstPage.getNext()) .request();
# Query drafts with a limit response = client.query_drafts(user_id, limit: 5) # Query the next page response = client.query_drafts(user_id, limit: 5, next: response['next'])
// Query drafts with a limit $response = $client->queryDrafts($userId, options: ["limit" => 5]); // Query the next page $response = $client->queryDrafts($userId, options: ["limit" => 5, "next" => $response["next"]]);
// Query drafts with a limit final firstPage = await client.queryDrafts( pagination: PaginationParams(limit: 5), ); // Query the next page final secondPage = await client.queryDrafts( pagination: PaginationParams(limit: 5, next: firstPage.next), );
Events
The following WebSocket events are available for draft messages:
draft.updated
, triggered when a draft message is updated.draft.deleted
, triggered when a draft message is deleted.
You can subscribe to these events using the Stream Chat SDK.
client.on("draft.updated", (event) => { // Handle event console.log("event", event); console.log("channel_cid", event.draft.channel_cid); });
let chatClient = ChatClient.shared let eventsController = chatClient.eventsController() eventsController.delegate = self public func eventsController(_ controller: EventsController, didReceiveEvent event: any Event) { if let event = event as? DraftUpdatedEvent { let threadId = event.draftMessage.threadId let channelId = event.cid // handle draft updated event } else if let event = event as? DraftDeletedEvent { let threadId = event.draftMessage.threadId let channelId = event.cid // handle draft deleted event } }
// Subscribe for 'draft.updated' events client.subscribeFor<DraftMessageUpdatedEvent> { event -> val channelId = event.draftMessage.cid val threadId = event.draftMessage.parentId // handle draft updated event } // Subscribe for 'draft.deleted' events client.subscribeFor<DraftMessageDeletedEvent> { event -> val channelId = event.draftMessage.cid val threadId = event.draftMessage.parentId // handle draft deleted event }
client.on(EventType.draftUpdated).listen((event) { // Handle event print('Event: $event'); print('Channel CID: ${event.draftMessage.channelCid}'); print('Parent ID: ${event.draftMessage.parentId}'); }); client.on(EventType.draftDeleted).listen((event) { // Handle event print('Event: $event'); print('Channel CID: ${event.draftMessage.channelCid}'); print('Parent ID: ${event.draftMessage.parentId}'); });