# 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.

<codetabs>

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

```js
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,
});
```

</codetabs-item>

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

```swift
// 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)")
}
```

</codetabs-item>

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

```kotlin
// 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 { /* ... */ }
```

</codetabs-item>

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

```go
// 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,
})

```

</codetabs-item>

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

```python
# 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
)
```

</codetabs-item>

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

```java
// 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();
```

</codetabs-item>

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

```ruby
# 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)
```

</codetabs-item>

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

```php
// 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);
```

</codetabs-item>

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

```dart
// 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);
```

</codetabs-item>

</codetabs>

## Deleting a draft message

You can delete a draft message for a channel or a thread as well.

<codetabs>

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

```js
// Channel draft
await channel.deleteDraft();

// Thread draft
await channel.deleteDraft({ parent_id: parentMessageId });
```

</codetabs-item>

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

```swift
// 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()
```

</codetabs-item>

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

```kotlin
// Channel draft
client.deleteDraftMessage(
    channelType = "messaging",
    channelId = "general",
    message = DraftMessage()
).enqueue { /* ... */ }

// Thread draft
client.deleteDraftMessage(
    channelType = "messaging",
    channelId = "general",
    message = DraftMessage(parentId = parentMessageId)
).enqueue { /* ... */ }
```

</codetabs-item>

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

```go
// Channel draft
resp, err := channel.DeleteDraft(ctx, user.ID, nil)

// Thread draft
resp, err := channel.DeleteDraft(ctx, user.ID, parentMessageID)
```

</codetabs-item>

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

```python
# Channel draft
await channel.delete_draft(user_id)

# Thread draft
await channel.delete_draft(user_id, parent_id=parent_id)
```

</codetabs-item>

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

```java
// 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();
```

</codetabs-item>

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

```ruby
# 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)
```

</codetabs-item>

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

```php
// Delete the draft message for a channel
$channel->deleteDraft($userId);

// Delete the draft message for a thread
$channel->deleteDraft($userId, $parentMessageId);
```

</codetabs-item>

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

```dart
// Delete the draft message for a channel
await channel.deleteDraft();

// Delete the draft message for a thread
await channel.deleteDraft(parentId: parentMessageId);
```

</codetabs>

## 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.

<codetabs>

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

```js
// Channel draft
const draft = await channel.getDraft();

// Thread draft
const threadDraft = await channel.getDraft({ parent_id: parentMessageId });
```

</codetabs-item>

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

```swift
// 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)")
    }
}
```

</codetabs-item>

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

```go
// Channel draft
resp, err := channel.GetDraft(ctx, nil, user.ID)

// Thread draft
resp, err := channel.GetDraft(ctx, parentMessageID, user.ID)
```

</codetabs-item>

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

```python
# Channel draft
response = await channel.get_draft(user_id)

# Thread draft
response = await channel.get_draft(user_id, parent_id=parent_id)
```

</codetabs-item>

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

```java
// 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();
```

</codetabs-item>

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

```ruby
# 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)
```

</codetabs-item>

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

```php
// Load the draft message for a channel
$response = $channel->getDraft($userId);

// Load the draft message for a thread
$response = $channel->getDraft($userId, $parentMessageId);
```

</codetabs-item>

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

```dart
// 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);
```

</codetabs>

## 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.

<codetabs>

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

```js
// 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 }],
});
```

</codetabs-item>

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

```swift
// 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
    }
}
```

</codetabs-item>

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

```kotlin
// 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 { /* ... */ }
```

</codetabs-item>

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

```go
// 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},
    },
})
```

</codetabs-item>

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

```python
# 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}],
)
```

</codetabs-item>

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

```java
// 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();
```

</codetabs-item>

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

```ruby
# 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 }]
)
```

</codetabs-item>

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

```php
// 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"]]
);
```

</codetabs-item>

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

```dart
// 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)],
);
```

</codetabs>

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.

<codetabs>

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

```js
// 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,
});
```

</codetabs-item>

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

```swift
// 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)")
    }
}
```

</codetabs-item>

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

```kotlin
// 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()
```

</codetabs-item>

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

```go
// 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,
})
```

</codetabs-item>

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

```python
# 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"]}
)
```

</codetabs-item>

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

```java
// 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();
```

</codetabs-item>

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

```ruby
# 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'])
```

</codetabs-item>

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

```php
// 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"]]);
```

</codetabs-item>

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

```dart
// 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),
);
```

</codetabs>

## 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.

<codetabs>

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

```js
client.on("draft.updated", (event) => {
  // Handle event
  console.log("event", event);
  console.log("channel_cid", event.draft.channel_cid);
});
```

</codetabs-item>

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

```swift
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
    }
}
```

</codetabs-item>

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

```kotlin
// 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
}
```

</codetabs-item>

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

```dart
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}');
});
```

</codetabs-item>

</codetabs>


---

This page was last updated at 2026-03-05T19:06:21.230Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/ruby/drafts/](https://getstream.io/chat/docs/ruby/drafts/).