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

<Tabs>

```js label="JavaScript"
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,
});
```

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

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

```dart label="Dart"
// Create/update a draft message in a channel
final channelDraft = await channel.createDraft(DraftMessage(
  text: 'This is a draft message',
));

// Create/update a draft message in a thread (parent message)
final threadDraft = await channel.createDraft(DraftMessage(
  text: 'This is a draft message',
  parentId: parentMessageId,
));
```

```python label="Python"
# Note: Draft creation is not yet available in the getstream Python SDK.
# Use the client-side SDKs (JavaScript, Swift, Kotlin, etc.) to create drafts.
```

```ruby label="Ruby"
# Note: Draft creation is a client-side only operation and is not available
# in the server-side Ruby SDK.
# Use the client-side SDKs (JavaScript, Swift, Kotlin, etc.) to create drafts.
```

```php label="PHP"
// Note: Draft creation is a client-side only operation and is not available
// in the server-side PHP SDK.
// Use the client-side SDKs (JavaScript, Swift, Kotlin, etc.) to create drafts.
```

```go label="Go"
// Note: CreateDraft is a client-side only operation and is not available
// in the server-side Go SDK.
// Use the client-side SDKs (JavaScript, Swift, Kotlin, etc.) to create drafts.
```

```java label="Java"
// Note: CreateDraft is a client-side only operation and is not available
// in the server-side Java SDK.
// Use the client-side SDKs (JavaScript, Swift, Kotlin, etc.) to create drafts.
```

</Tabs>

## Deleting a draft message

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

<Tabs>

```js label="JavaScript"
// Channel draft
await channel.deleteDraft();

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

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

```swift label="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()
```

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

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

```python label="Python"
# Channel draft
channel.delete_draft(user_id=user_id)

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

```ruby label="Ruby"
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Delete the draft message for a channel
client.chat.delete_draft('messaging', channel_id, '', user_id)

# Delete the draft message for a thread
client.chat.delete_draft('messaging', channel_id, parent_message_id, user_id)
```

```php label="PHP"
// Delete the draft message for a channel
$client->deleteDraft("messaging", "general", parentID: "", userID: $userId);

// Delete the draft message for a thread
$client->deleteDraft("messaging", "general", parentID: $parentMessageId, userID: $userId);
```

```go label="Go"
channel := client.Chat().Channel("messaging", "channel-id")

// Channel draft
_, err := channel.DeleteDraft(ctx, &getstream.DeleteDraftRequest{
	UserID: getstream.PtrTo(userID),
})

// Thread draft
_, err = channel.DeleteDraft(ctx, &getstream.DeleteDraftRequest{
	UserID:   getstream.PtrTo(userID),
	ParentID: getstream.PtrTo(parentMessageID),
})
```

```java label="Java"
// Delete the draft message for a channel
chat.channel(channelType, channelId)
    .deleteDraft(DeleteDraftRequest.builder()
        .UserID(userId)
        .build());

// Delete the draft message for a thread
chat.channel(channelType, channelId)
    .deleteDraft(DeleteDraftRequest.builder()
        .UserID(userId)
        .ParentID(parentMessageId)
        .build());
```

</Tabs>

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

<Tabs>

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

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

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

```dart label="Dart"
// Load the draft message for a channel
final channelDraft = await channel.getDraft();

// Load the draft message for a thread
final threadDraft = await channel.getDraft(parentId: parentMessageId);
```

```python label="Python"
# Channel draft
response = channel.get_draft(user_id=user_id)

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

```ruby label="Ruby"
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Load the draft message for a channel
response = client.chat.get_draft('messaging', channel_id, '', user_id)

# Load the draft message for a thread
response = client.chat.get_draft('messaging', channel_id, parent_message_id, user_id)
```

```php label="PHP"
// Load the draft message for a channel
$response = $client->getDraft("messaging", "general", parentID: "", userID: $userId);

// Load the draft message for a thread
$response = $client->getDraft("messaging", "general", parentID: $parentMessageId, userID: $userId);
```

```go label="Go"
channel := client.Chat().Channel("messaging", "channel-id")

// Channel draft
resp, err := channel.GetDraft(ctx, &getstream.GetDraftRequest{
	UserID: getstream.PtrTo(userID),
})

// Thread draft
resp, err = channel.GetDraft(ctx, &getstream.GetDraftRequest{
	UserID:   getstream.PtrTo(userID),
	ParentID: getstream.PtrTo(parentMessageID),
})
```

```java label="Java"
// Load the draft message for a channel
var draftResponse = chat.channel(channelType, channelId)
    .getDraft(GetDraftRequest.builder()
        .UserID(userId)
        .build());

// Load the draft message for a thread
var threadDraftResponse = chat.channel(channelType, channelId)
    .getDraft(GetDraftRequest.builder()
        .UserID(userId)
        .ParentID(parentMessageId)
        .build());
```

</Tabs>

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

<Tabs>

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

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

```swift label="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
    }
}
```

```dart label="Dart"
// Query all user drafts
final allDrafts = await client.queryDrafts();

// Query drafts for certain channels and sort
final filteredDrafts = await client.queryDrafts(
  filter: Filter.in_(
    'channel_cid',
    const ['messaging:channel-1', 'messaging:channel-2'],
  ),
  sort: const [SortOption<Draft>.desc(DraftSortKey.createdAt)],
);
```

```python label="Python"
from getstream.models import SortParamRequest

# Query all user drafts
response = client.chat.query_drafts(user_id=user_id, limit=10)

# Query drafts for certain channels and sort
response = client.chat.query_drafts(
    user_id=user_id,
    filter={
        "channel_cid": {"$in": ["messaging:channel-1", "messaging:channel-2"]},
    },
    sort=[SortParamRequest(field="created_at", direction=1)],
)
```

```ruby label="Ruby"
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Query all user drafts
response = client.chat.query_drafts(Models::QueryDraftsRequest.new(user_id: user_id))

# Query drafts for certain channels and sort
response = client.chat.query_drafts(Models::QueryDraftsRequest.new(
  user_id: user_id,
  filter: { 'channel_cid' => { '$in' => ['messaging:channel-1', 'messaging:channel-2'] } },
  sort: [Models::SortParamRequest.new(field: 'created_at', direction: -1)]
))
```

```php label="PHP"
// Query all user drafts
$response = $client->queryDrafts(new Models\QueryDraftsRequest(userID: $userId));

// Query drafts for certain channels and sort
$response = $client->queryDrafts(new Models\QueryDraftsRequest(
    userID: $userId,
    filter: (object)["channel_cid" => (object)['$in' => ["messaging:channel-1", "messaging:channel-2"]]],
    sort: [new Models\SortParamRequest(field: "created_at", direction: 1)],
));

// Query drafts with pagination
$response = $client->queryDrafts(new Models\QueryDraftsRequest(
    userID: $userId,
    limit: 1,
));

// Query drafts with pagination and next
$response = $client->queryDrafts(new Models\QueryDraftsRequest(
    userID: $userId,
    limit: 1,
    next: $response->getData()->next,
));
```

```go label="Go"
// Query all user drafts
resp, err := client.Chat().QueryDrafts(ctx, &getstream.QueryDraftsRequest{
	UserID: getstream.PtrTo(userID),
	Limit:  getstream.PtrTo(10),
})

// Query drafts for certain channels and sort
resp, err = client.Chat().QueryDrafts(ctx, &getstream.QueryDraftsRequest{
	UserID: getstream.PtrTo(userID),
	Filter: map[string]any{
		"channel_cid": map[string]any{
			"$in": []string{"messaging:channel-1", "messaging:channel-2"},
		},
	},
	Sort: []getstream.SortParamRequest{
		{Field: getstream.PtrTo("created_at"), Direction: getstream.PtrTo(1)},
	},
})
```

```java label="Java"
// Query all user drafts
var queryResponse = chat.queryDrafts(QueryDraftsRequest.builder()
    .userID(userId)
    .limit(10)
    .build()).execute().getData();

// Query drafts for certain channels and sort
var filteredResponse = chat.queryDrafts(QueryDraftsRequest.builder()
    .userID(userId)
    .filter(Map.of("channel_cid", Map.of("$in", List.of("messaging:channel-1", "messaging:channel-2"))))
    .sort(List.of(SortParamRequest.builder().field("created_at").direction(-1).build()))
    .build()).execute().getData();
```

</Tabs>

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.

<Tabs>

```js label="JavaScript"
// 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,
});
```

```kotlin label="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()
```

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

```dart label="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),
);
```

```python label="Python"
# Query drafts with a limit
first_page = client.chat.query_drafts(user_id=user_id, limit=5)

# Query the next page
second_page = client.chat.query_drafts(
    user_id=user_id, limit=5, next=first_page.data.next
)
```

```ruby label="Ruby"
require 'getstream_ruby'
Models = GetStream::Generated::Models

# Query drafts with a limit
response = client.chat.query_drafts(Models::QueryDraftsRequest.new(user_id: user_id, limit: 5))

# Query the next page
response = client.chat.query_drafts(Models::QueryDraftsRequest.new(
  user_id: user_id,
  limit: 5,
  next: response.next
))
```

```php label="PHP"
// Query drafts with a limit
$response = $client->queryDrafts(new Models\QueryDraftsRequest(
    userID: $userId,
    limit: 5,
));

// Query the next page
$response = $client->queryDrafts(new Models\QueryDraftsRequest(
    userID: $userId,
    limit: 5,
    next: $response->getData()->next,
));
```

```go label="Go"
// Query drafts with a limit
resp, err := client.Chat().QueryDrafts(ctx, &getstream.QueryDraftsRequest{
	UserID: getstream.PtrTo(userID),
	Limit:  getstream.PtrTo(5),
})

// Query the next page
resp, err = client.Chat().QueryDrafts(ctx, &getstream.QueryDraftsRequest{
	UserID: getstream.PtrTo(userID),
	Limit:  getstream.PtrTo(5),
	Next:   resp.Data.Next,
})
```

```java label="Java"
// Query drafts with a limit
var firstPage = chat.queryDrafts(QueryDraftsRequest.builder()
    .userID(userId)
    .limit(5)
    .build()).execute().getData();

// Query the next page
var secondPage = chat.queryDrafts(QueryDraftsRequest.builder()
    .userID(userId)
    .limit(5)
    .next(firstPage.getNext())
    .build()).execute().getData();
```

</Tabs>

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

<Tabs>

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

```kotlin label="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
}
```

```swift label="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
    }
}
```

```dart label="Dart"
client.on(EventType.draftUpdated).listen((event) {
  // Handle event
  print('Event: $event');
  print('Channel CID: ${event.draft?.channelCid}');
  print('Parent ID: ${event.draft?.parentId}');
});

client.on(EventType.draftDeleted).listen((event) {
  // Handle event
  print('Event: $event');
  print('Channel CID: ${event.draft?.channelCid}');
  print('Parent ID: ${event.draft?.parentId}');
});
```

</Tabs>


---

This page was last updated at 2026-07-13T13:43:15.010Z.

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