---
source_url: https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/drafts
title: Create message drafts
updated_at: 2026-05-25T11:25:23.791Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Create message drafts

`MessageDraft` represents an unpublished message. Use it to:

* [Edit text](#update-message-text) before publishing
* Add [channel references](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/channels/references), [user mentions](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/users/mentions), and [links](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/links)
* Attach files

Display message elements (mentions, references, links) in your UI by adding a [message draft change listener](#add-message-draft-change-listener).

### Basics

Message drafts consist of `MessageElement` objects, which can be either instances of `PlainText` or `Link`.

`PlainText` are simple strings, while `Link` elements are used for user mentions, channel references, and URLs. They contain a text and a reference to the linked element regardless if it's a user, a channel, or a URL.

Each `Link` implements a `MentionTarget` interface, which defines the type of mention. Available targets include:

* `MentionTarget.User(val userId: String)`
* `MentionTarget.Channel(val channelId: String)`
* `MentionTarget.Url(val url: String)`

:::note Store draft messages locally
Chat SDK does not persist drafts. Implement your own local storage to save drafts across channel switches.
:::

### Class diagram

### Example

Consider the message `Hey, I sent Alex this link on the #offtopic channel.` where:

* `Alex` is a reference to the user with the ID of `alex_d`
* `link` is a URL of the `www.pubnub.com` website
* `#offtopic` is a reference to the channel with the ID of `group.offtopic`

The list of `MessageElement` objects returned by the `MessageDraftChangeListener` is as follows:

| Part of Message | Element Type | Code Used to Create |
| --- | --- | --- |
| Hey, I sent | `PlainText` | [messageDraft.update(text = "Hey, I sent Alex this link on the #offtopic channel.")](#update-message-text) |
| Alex | `Link` | [messageDraft.addMention(offset = 12, length = 4, target = MentionTarget.User(userId = "alex_d"))](#add-message-element) |
| this | `PlainText` | [messageDraft.update(text = "Hey, I sent Alex this link on the #offtopic channel.")](#update-message-text) |
| link | `Link` | [messageDraft.addMention(offset = 22, length = 4, target = MentionTarget.Url(url = "www.pubnub.com"))](#add-message-element) |
| on the | `PlainText` | [messageDraft.update(text = "Hey, I sent Alex this link on the #offtopic channel.")](#update-message-text) |
| #offtopic | `Link` | [messageDraft.addMention(offset = 34, length = 9, target = MentionTarget.Channel(channelId = "group.offtopic"))](#add-message-element) |
| channel. | `PlainText` | [messageDraft.update(text = "Hey, I sent Alex this link on the #offtopic channel.")](#update-message-text) |

### Internal mention format

By internally leveraging a Markdown-like syntax, the message draft format integrates links directly into the message text using the pattern `[link text](https://www.pubnub.com/docs/link target)` understood by the Kotlin Chat SDK.

| Mention Type | Example |
| --- | --- |
| User | `[John Doe](https://www.pubnub.com/docs/pn-user://john_doe)` |
| Channel | `[General Chat](https://www.pubnub.com/docs/pn-channel://group.chat.123)` |
| URL | `[PubNub](https://www.pubnub.com)` |

Custom schemas like `pn-user://` and `pn-channel://` are used to identify user and channel mentions, while traditional URLs are supported as-is.

:::warning Adding message elements
This syntax is internal only. To add elements, use [addMention()](#add-message-element).
:::

## Create a draft message

`createMessageDraft()` creates a message draft ([MessageDraft object](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/message-draft)) that can consist of:

* Plain text
* [Files](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/files)
* [Mentioned users](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/users/mentions)
* [Referenced channels](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/channels/references)
* [Links](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/links)

### Method signature

This method has the following signature:

```kotlin
channel.createMessageDraft(
  userSuggestionSource: UserSuggestionSource = UserSuggestionSource.CHANNEL,
  isTypingIndicatorTriggered: Boolean = true,
  userLimit: Int = 10,
  channelLimit: Int = 10
):
```

#### Input

| Parameter | Description |
| --- | --- |
| `userSuggestionSource`Type: `UserSuggestionSource.CHANNEL` or `UserSuggestionSource.GLOBAL`Default: `UserSuggestionSource.CHANNEL` | This parameter refers to the [Mentions](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/users/mentions) feature. Data source from which you want to retrieve users. You can choose either the list of channel members (`.CHANNEL`) or users on the app's Admin Portal keyset (`.GLOBAL`). |
| `isTypingIndicatorTriggered`Type: `Boolean`Default: `true` | This parameter refers to the [Typing Indicator](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/channels/typing-indicator) feature. Defines if the typing indicator should be enabled when writing the message. |
| `userLimit`Type: `Int`Default: `10` | This parameter refers to the [Mentions](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/users/mentions) feature. Maximum number of usernames (`name` field from the `User` object) you can mention in one message. The default value is `10`, the min is `1`, and max is `100`. |
| `channelLimit`Type: `Int`Default: `10` | This parameter refers to the [References](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/channels/references) feature. Maximum number of channel names (`name` field from the `Channel` object) you can reference in one message. The default value is `10`, the min is `1`, and max is `100`. |

#### Output

| Type | Description |
| --- | --- |
| `MessageDraft` | Created [MessageDraft object](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/message-draft) with the content of the message, all links, referenced channels, mentioned users and their names. |

### Sample code

Create a draft message containing just plain text.

```kotlin
val messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered = channel.type != ChannelType.PUBLIC)
```

## Add message draft change listener

Add a `MessageDraftChangeListener` to receive draft content changes and suggestions for mentions, channel references, and links.

### Method signature

This method has the following signature:

```kotlin
messageDraft.addChangeListener(listener: MessageDraftChangeListener)

// interface to implement
fun interface MessageDraftChangeListener {
  fun onChange(messageElements: List<MessageElement>, suggestedMentions: PNFuture<List<SuggestedMention>>)
}
```

#### Input

| Parameter | Description |
| --- | --- |
| `listener`Type: [MessageDraftChangeListener](#messagedraftchangelistener) | The listener that receives the most current message elements and suggestions list. |

##### MessageDraftChangeListener

| Parameter | Description |
| --- | --- |
| `onChange(messageElements: List<MessageElement>, suggestedMentions: PNFuture<List<SuggestedMention>>)`Type: function | messageElements: List<MessageElement> - this parameter is a list of MessageElement objects, representing the current state of the message draft. This could contain a mix of plain text and links, channel references, or user mentions., suggestedMentions: PNFuture<List<SuggestedMention>> - this parameter is a PNFuture containing a list of SuggestedMention objects. These are potential suggestions for message elements based on the current text in the draft. |

##### SuggestedMention

A `SuggestedMention` represents a potential mention suggestion received from [MessageDraftChangeListener](#add-message-draft-change-listener).

| Parameter | Description |
| --- | --- |
| `offset`Type: `Int` | The position from the start of the message draft where the message elements starts. It's counted from the beginning of the message (including spaces), with `0` as the first character. |
| `replaceFrom`Type: `String` | The original text at the given offset in the message draft text. |
| `replaceWith`Type: `String` | The suggested replacement for the `replaceFrom` text. |
| `target`Type: `MentionTarget` | The message element type. Available types include: MentionTarget.User(val userId: String), MentionTarget.Channel(val channelId: String), MentionTarget.Url(val url: String) |

#### Output

This method doesn't return any data.

### Sample code

Add the listener to your message draft.

```kotlin
// create a message draft
val messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered = channel.type != ChannelType.PUBLIC)

// add the listener
val listener = { elements: List<MessageElement>, suggestedMentions: PNFuture<List<SuggestedMention>> ->
        updateUI(elements) // updateUI is your own function for updating UI
        suggestedMentions.async { result ->
            result.onSuccess { updateSuggestions(it) } // updateSuggestions is your own function for displaying suggestions
        }
    }
messageDraft.addChangeListener(listener)
```

## Remove message draft change listener

Remove a previously added `MessageDraftChangeListener`.

### Method signature

This method has the following signature:

```kotlin
messageDraft.removeChangeListener(listener: MessageDraftChangeListener)
```

#### Input

| Parameter | Description |
| --- | --- |
| `listener`Type: [MessageDraftChangeListener](#messagedraftchangelistener) | The listener to remove. |

#### Output

This method doesn't return any data.

### Sample code

Remove a listener from your message draft.

```kotlin
// create a message draft
val messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered = channel.type != ChannelType.PUBLIC)

// add the listener
val listener = { elements: List<MessageElement>, suggestedMentions: PNFuture<List<SuggestedMention>> ->
        updateUI(elements) // updateUI is your own function for updating UI
        suggestedMentions.async { result ->
            result.onSuccess { updateSuggestions(it) } // updateSuggestions is your own function for displaying suggestions
        }
    }
messageDraft.addChangeListener(listener)

// remove the listener
messageDraft.removeChangeListener(listener)
```

## Add message element

`addMention()` adds a user mention, channel reference or a link specified by a mention target at a given offset.

### Method signature

This method has the following signature:

```kotlin
messageDraft.addMention(
  offset: Int, 
  length: Int, 
  target: MentionTarget
)
```

#### Input

| Parameter | Description |
| --- | --- |
| `offset` *Type: `Int`Default: n/a | Position of a character in a message where the message element you want to insert starts. It's counted from the beginning of the message (including spaces), with `0` as the first character. |
| `length` *Type: `Int`Default: n/a | Number of characters the message element should occupy in the draft message's text. |
| `target` *Type: `MentionTarget`Default: n/a | Message element type. Available types include: MentionTarget.User(val userId: String), MentionTarget.Channel(val channelId: String), MentionTarget.Url(val url: String) |

#### Output

This method returns no output data.

### Sample code

Create the `Hello Alex! I have sent you this link on the #offtopic channel.` message where `Alex` is a user mention, `link` is a URL, and `#offtopic` is a channel reference.

```kotlin
// create an empty message draft
val messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered = channel.type != ChannelType.PUBLIC)

// add a text
messageDraft.update(text = "Hello Alex!")

// add a user mention to the string 'Alex'
messageDraft.addMention(offset = 6, length = 4, target = MentionTarget.User(userId = "alex_d"))

// change the text
messageDraft.update(text = "Hello Alex! I have sent you this link on the #offtopic channel.")

// add a link to the string 'link'
messageDraft.addMention(offset = 33, length = 4, target = MentionTarget.Url(url = "www.pubnub.com"))

// add a channel mention to the string '#offtopic'
messageDraft.addMention(offset = 45, length = 9, target = MentionTarget.Channel(channelId = "group.offtopic"))
```

## Remove message element

`removeMention()` removes a user mention, channel reference, or a link at a given offset.

### Method signature

This method has the following signature:

```kotlin
messageDraft.removeMention(offset: Int)
```

#### Input

| Parameter | Description |
| --- | --- |
| `offset` *Type: `Int`Default: n/a | Position of the first character of the message element you want to remove. |

:::warning Offset value
If you don't provide the position of the first character of the message element to remove, it isn't removed.
:::

#### Output

This method returns no output data.

### Sample code

Remove the URL element from the word `link` in the `Hello Alex! I have sent you this link on the #offtopic channel.` message.

```kotlin
// assume the message reads
// Hello Alex! I have sent you this link on the #offtopic channel.

// remove the link mention
messageDraft.removeMention(offset = 33)
```

## Update message text

`update()` replaces the text of a [draft message](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/drafts) with new content.

:::warning Removing message elements
The SDK preserves message elements when possible. If element text is modified, that element is removed.
:::

### Method signature

This method has the following signature:

```kotlin
messageDraft.update(text: String)
```

#### Input

| Parameter | Description |
| --- | --- |
| `text` *Type: `string`Default: n/a | Text of the message that you want to update. |

#### Output

This method returns no output data.

### Sample code

Change the message `I sent Alex this picture.` to `I did not send Alex this picture.` where `Alex` is a user mention.

```kotlin
// the message reads:
// I sent [Alex] this picture.
// where [Alex] is a user mention
messageDraft.update(text = "I did not send Alex this picture.")
// the message now reads: 
// I did not send [Alex] this picture.
// the mention is preserved because its text wasn't changed
```

:::note Mention text changes
Changing mention text removes that mention. For finer control, use [Insert message text](#insert-message-text) and [Remove message text](#remove-message-text).
:::

## Insert suggested message element

Insert a message element from the [MessageDraftChangeListener](#add-message-draft-change-listener) into the `MessageDraft`.

:::warning Text must match
`SuggestedMention.replaceFrom` must match the draft text at the specified position, or an exception is thrown.
:::

### Method signature

This method has the following signature:

```kotlin
messageDraft.insertSuggestedMention(
  mention: SuggestedMention, 
  text: String
)
```

#### Input

| Parameter | Description |
| --- | --- |
| `mention` *Type: [SuggestedMention](#suggestedmention)Default: n/a | A user, channel, or URL suggestion obtained from [MessageDraftChangeListener](#add-message-draft-change-listener). |
| `text` *Type: `string`Default: n/a | The text you want the message element to display. |

#### Output

This method returns no output data.

### Sample code

Register a listener and insert a suggested element.

```kotlin
// create a message draft
val messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered = channel.type != ChannelType.PUBLIC)

// add the listener
val listener = { elements: List<MessageElement>, suggestedMentions: PNFuture<List<SuggestedMention>> ->
        updateUI(elements) // updateUI is your own function for updating UI
        suggestedMentions.async { result ->
            result.onSuccess { updateSuggestions(it) } // updateSuggestions is your own function for displaying suggestions
        }
    }
messageDraft.addChangeListener(listener)

// when the user selects a suggestion in the UI:
messageDraft.insertSuggestedMention(suggestion, suggestion.replaceWith)
```

## Insert message text

`insertText()` inserts plain text in the [draft message](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/drafts) at the specified offset.

:::warning Removing message elements
Inserting text at an existing message element position removes that element.
:::

### Method signature

This method has the following signature:

```kotlin
messageDraft.insertText(
    offset: Int, 
    text: String
)
```

#### Input

| Parameter | Description |
| --- | --- |
| `offset` *Type: `Int`Default: n/a | Position of a character in a message where the text you want to insert starts. It's counted from the beginning of the message (including spaces), with `0` as the first character. |
| `text` *Type: `string`Default: n/a | Text that you want to insert. |

#### Output

This method returns no output data.

### Sample code

In the message `Check this support article https://www.support-article.com/.`, add the word `out` between the words `Check` and `this`.

```kotlin
// the message reads:
// Check this support article https://www.support-article.com/.
messageDraft.insertText(6, "out ")
// the message now reads: 
// Check out this support article https://www.support-article.com/.
```

## Remove message text

`removeText()` removes plain text from the [draft message](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/drafts) at the specified offset.

:::warning Removing message elements
Removing text at an existing message element position removes that element.
:::

### Method signature

This method has the following signature:

```kotlin
messageDraft.removeText(
    offset: Int, 
    length: Int
)
```

#### Input

| Parameter | Description |
| --- | --- |
| `offset` *Type: `Int`Default: n/a | Position of a character in a message where the text you want to insert starts. It's counted from the beginning of the message (including spaces), with `0` as the first character. |
| `length` *Type: `Int`Default: n/a | How many characters to remove, starting at the given `offset`. |

#### Output

This method returns no output data.

### Sample code

In the message `Check out this support article https://www.support-article.com/.`, remove the word `out`.

```kotlin
// the message reads:
// Check out this support article https://www.support-article.com/..
messageDraft.removeText(5, 4)
// the message now reads: 
// Check this support article https://www.support-article.com/.
```

## Send a draft message

`send()` publishes the draft message with all [mentioned users](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/users/mentions), [links](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/links), and [referenced channels](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/channels/references). Mentioning users also emits [mention events](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/custom-events#events-for-mentions).

### Method signature

This method has the following signature:

```kotlin
messageDraft.send(
    params: SendTextParams = SendTextParams(),
): PNFuture<PNPublishResult>
```

#### Input

| Parameter | Description |
| --- | --- |
| `params`Type: `SendTextParams`Default: `SendTextParams()` | Object with additional send options. |
| `params.meta`Type: `Map<String, Any>?`Default: `null` | Additional details of the request. |
| `params.shouldStore`Type: `Boolean`Default: `true` | If `true`, the messages are stored in [Message Persistence](https://www.pubnub.com/docs/general/storage) (PubNub storage). If `shouldStore` is not specified, the Message Persistence configuration specified on the Admin Portal keyset is used. |
| `params.usePost`Type: `Boolean`Default: `false` | When `true`, the SDK uses HTTP POST to publish the messages. The message is sent in the BODY of the request instead of the query string when HTTP GET is used. The messages are also compressed to reduce their size. |
| `params.ttl`Type: `Int?`Default: `null` | Defines if / how long (in hours) the message should be stored in Message Persistence. If shouldStore = true, and ttl = 0, the message is stored with no expiry time., If shouldStore = true and ttl = X, the message is stored with an expiry time of X hours unless you have message retention set to Unlimited on your keyset configuration in the Admin Portal., If shouldStore = false, the ttl parameter is ignored., If ttl is not specified, then the expiration of the message defaults back to the expiry value for the keyset. |

Suppose a user adds elements to the message draft, such as links, quotes, other user mentions, or channel references. In that case, these are not explicitly passed in the `send()` method but get added to the [MessageDraft](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/learn/chat-entities/message-draft) object through the [addMention()](#add-message-element) and [addQuote()](https://www.pubnub.com/docs/chat/kotlin-chat-sdk/build/features/messages/quotes#quote-message) methods.

#### Output

| Type | Description |
| --- | --- |
| `PNFuture<PNPublishResult>` | Returned object that contains the timetoken of the message. |

### Sample code

Send a draft message containing just plain text.

```kotlin
// create a draft message
val messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered = channel.type != ChannelType.PUBLIC)

// add text
messageDraft.update(text = "Hello!")
// send the message
messageDraft.send()
```