---
source_url: https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/messages/drafts
title: Create message drafts
updated_at: 2026-06-04T11:09:43.012Z
---

> 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/swift-chat-sdk/build/features/channels/references), [user mentions](https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/users/mentions), and [links](https://www.pubnub.com/docs/chat/swift-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` elements are simple strings. `link` elements are used for user mentions, channel references, and URLs. Each link contains text and a reference to the linked element.

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

* `MentionTarget.user(userId: String)`
* `MentionTarget.channel(channelId: String)`
* `MentionTarget.url(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 Swift 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
You don't use this Markdown-like syntax when adding message elements. It's only a representation of your message elements under the hood.
For more information, refer to [Add message element](#add-message-element).
:::

## Create a draft message

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

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

### Method signature

This method has the following signature:

```swift
channel.createMessageDraft(
  userSuggestionSource: UserSuggestionSource = .channel,
  isTypingIndicatorTriggered: Bool = true,
  userLimit: Int = 10,
  channelLimit: Int = 10
) -> MessageDraftImpl
```

#### 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/swift-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: `Bool`Default: `true` | This parameter refers to the [Typing Indicator](https://www.pubnub.com/docs/chat/swift-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/swift-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/swift-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

| Parameter | Description |
| --- | --- |
| `MessageDraftType` | Instance of `MessageDraftType`, which represents a draft version of a message with the content, all links, referenced channels, mentioned users and their names. |

### Sample code

Create a draft message containing just plain text.

```swift
// Assumes a "ChannelImpl" reference named "channel"
let messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered: channel.type != .public)
```

## Create a thread message draft

`createThreadMessageDraft()` creates a [MessageDraft](https://www.pubnub.com/docs/chat/swift-chat-sdk/learn/chat-entities/message-draft) for composing a reply inside a message thread. Call it on a `Message` or `ThreadMessage` object. The resulting draft targets the message's thread channel and supports the same features as a regular draft: mentions, channel references, links, and file attachments.

### Method signature

This method has the following signature:

```swift
message.createThreadMessageDraft(
  userSuggestionSource: UserSuggestionSource = .channel,
  isTypingIndicatorTriggered: Bool = true,
  userLimit: Int = 10,
  channelLimit: Int = 10
) -> MessageDraftImpl
```

#### Input

| Parameter | Description |
| --- | --- |
| `userSuggestionSource`Type: `UserSuggestionSource = .channel` or `UserSuggestionSource = .global`Default: `UserSuggestionSource = .channel` | Data source from which you want to retrieve users for mention suggestions. Choose either the list of channel members (`.channel`) or all users on your Admin Portal keyset (`.global`). |
| `isTypingIndicatorTriggered`Type: `Bool`Default: `true` | Defines if the [typing indicator](https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/channels/typing-indicator) should be enabled while writing the message. |
| `userLimit`Type: `Int`Default: `10` | 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` | 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

| Parameter | Description |
| --- | --- |
| `MessageDraftType` | Instance of `MessageDraftType` targeting the message's thread channel. |

### Sample code

Create a draft reply for a thread started by an existing message, then send it.

```swift
// Assuming you have a reference of type "MessageImpl" named "message"
let threadDraft = try await message.createThreadMessageDraft(
    isTypingIndicatorTriggered: true
)

threadDraft.update(text: "This is my thread reply draft")

let timetoken = try await threadDraft.send()
```

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

```swift
messageDraft.addChangeListener(_ listener: MessageDraftChangeListener)

public protocol MessageDraftChangeListener: AnyObject {
  /// Called when there is a change in the message elements or suggested mentions.
  func onChange(messageElements: [MessageElement], suggestedMentions: any FutureObject<[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: [MessageElement], suggestedMentions: any FutureObject<[SuggestedMention]>)`Type: function | messageElements: [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: any FutureObject<[SuggestedMention]> - this parameter is a FutureObject 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(userId: String), MentionTarget.channel(channelId: String), MentionTarget.url(url: String) |

#### Output

This method doesn't return any data.

### Sample code

Add the listener to your message draft.

```swift
// Assumes a "ChannelImpl" reference named "channel"
let messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered: channel.type != .public)
let listener = ClosureMessageDraftChangeListener { elements, suggestedMentions in
  // updateUI(with:) is your own function for updating UI
  updateUI(with: elements)
    
  suggestedMentions.async { result in
    switch result {
    case .success(let mentions):
      // updateSuggestions(with:) is your own function for displaying suggestions
      updateSuggestions(with: mentions)
    case .failure(let error):
      print("Error retrieving suggestions: \(error)")
    }
  }
}
  
messageDraft.addChangeListener(listener)
  
func updateUI(with elements: [MessageElement]) {}
func updateSuggestions(with: [SuggestedMention]) {}
```

## Remove message draft change listener

Remove a previously added `MessageDraftChangeListener`.

### Method signature

This method has the following signature:

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

```swift
// Create a message draft.
// Assuming you have a reference of type "ChannelImpl" named "channel"
let messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered: channel.type != .public)
// Define the listener
let listener = ClosureMessageDraftChangeListener() { elements, suggestedMentions in
  // updateUI(with:) is your own function for updating UI
  updateUI(with: elements)

  suggestedMentions.async { result in
    switch result {
    case .success(let mentions):
      // updateSuggestions(with:) is your own function for displaying suggestions
      updateSuggestions(with: mentions)
    case .failure(let error):
      print("Error retrieving suggestions: \(error)")
    }
  }
}

// Add the listener to the message draft
messageDraft.addChangeListener(listener)
// Remove the listener from the message draft
messageDraft.removeChangeListener(listener)

func updateUI(with elements: [MessageElement]) {}
func updateSuggestions(with: [SuggestedMention]) {}
```

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

```swift
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(userId: String), MentionTarget.channel(channelId: String), MentionTarget.url(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.

```swift
// Assumes a "ChannelImpl" reference named "channel"
let messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered: channel.type != .public)
messageDraft.update(text: "Hello Alex!")
messageDraft.addMention(offset: 6, length: 4, target: .user(userId: "alex_d"))
messageDraft.update(text: "Hello Alex! I have sent you this link on the #offtopic channel.")
messageDraft.addMention(offset: 33, length: 4, target: .url(url: "www.pubnub.com"))
messageDraft.addMention(offset: 45, length: 9, target: .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:

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

```swift
// Assumes a "MessageDraftImpl" reference named "messageDraft"
  
// 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/swift-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:

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

```swift
// Assumes a "MessageDraftImpl" reference named "messageDraft"
  
// 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` object.

:::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:

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

```swift
// Create a message draft.
// Assuming you have a reference of type "ChannelImpl" named "channel"
let messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered: channel?.type != .public)
// Create the listener
let listener = ClosureMessageDraftChangeListener() { elements, suggestedMentions in
  // updateUI() is your own function for updating the UI:
  updateUI(elements)
  suggestedMentions.async { result in
    switch result {
    case .success(let mentions):
      // updateSuggestions() is your own function for displaying suggestions
      updateSuggestions(mentions)
    case .failure(let error):
      print("Error retrieving suggestions: \(error)")
    }
  }
}

messageDraft.addChangeListener(listener)

// Assuming that getSelectedSuggestion() is your own function that returns a suggestion selected by the user.
// When the user selects a suggestion in the UI:
if let suggestion = getSelectedSuggestion() {
    messageDraft.insertSuggestedMention(mention: suggestion, text: suggestion.replaceWith)
}

func updateUI(_ elements: [MessageElement]) {}
func updateSuggestions(_ mentions: [SuggestedMention]) {}
func getSelectedSuggestion() -> SuggestedMention? { nil }
```

## Insert message text

`insertText()` inserts plain text in the [draft message](https://www.pubnub.com/docs/chat/swift-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:

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

```swift
// Assumes a "MessageDraftImpl" reference named "messageDraft"
  
// The message reads: "Check this support article https://www.support-article.com/."
messageDraft.insertText(offset: 6, text: "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/swift-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:

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

```swift
// Assumes a "MessageDraftImpl" reference named "messageDraft"
  
// The message reads: "Check out this support article https://www.support-article.com/."
messageDraft.removeText(offset: 5, length: 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/swift-chat-sdk/build/features/users/mentions), [links](https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/messages/links), and [referenced channels](https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/channels/references). Mentioning users also emits [mention events](https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/custom-events#events-for-mentions).

### Method signature

This method has the following signature:

```swift
messageDraft.send(
  params: SendTextParams = SendTextParams()
) async throws -> Timetoken
```

The `SendTextParams` struct groups common publishing options:

```swift
public struct SendTextParams {
    public var meta: [String: JSONCodable]?
    public var shouldStore: Bool
    public var usePost: Bool
    public var ttl: Int?
    public var customPushData: [String: String]?
}
```

#### Input

| Parameter | Description |
| --- | --- |
| `params`Type: `SendTextParams`Default: `SendTextParams()` | Publishing options grouped in a single struct. See [sendText()](https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/messages/send-receive#method-signature) for field descriptions. |

Message elements (links, quotes, mentions, channel references) are not passed to `send()` directly. Add them to the [MessageDraft](https://www.pubnub.com/docs/chat/swift-chat-sdk/learn/chat-entities/message-draft) object through [addMention()](#add-message-element) and [addQuote()](https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/messages/quotes#quote-message).

#### Output

| Parameter | Description |
| --- | --- |
| `Timetoken` | Timetoken of the message. |

### Sample code

Send a draft message containing just plain text.

```swift
// Assumes a "ChannelImpl" reference named "channel"
Task {
  let messageDraft = channel.createMessageDraft(isTypingIndicatorTriggered: channel.type != .public)
  messageDraft.update(text: "Hello!")
  try await messageDraft.send()
}
```