Kord Extensions Help

Modals

The term "Modal" refers to a pop-up window within the Discord client. Modals contain a title and a number of Components.

Type

Input

Has ID

Action Row

❌ No

Text Input (Line)

Single Line

✅ Yes

Text Input (Paragraph)

Multiple Lines

✅ Yes

As the ModalForm type attempts to expose an API similar to the concept of UI forms; Modal components are referred to as Widgets, to be placed on a grid.

As of this writing, this concept hasn't yet been fully explored. We plan to expand upon the concept later.

Limitations

There are a number of limitations to keep in mind when working with Discord modals.

  • Discord won't tell the bot when a user closes a Modal without submitting it. The only way around this is to use a timeout, and assume that the Modal won't be submitted once it expires.

  • As Modals use interactions, they must be submitted within 15 minutes. The interaction will fail if the user takes too long to submit a Modal.

  • Modals have a very limited number of compatible Widgets. They used to support Select Menus, but this support was suddenly removed without explanation.

  • Modals may only be sent as the first response to an interaction. This means they must be created and sent within 5 seconds, and they cannot be sent once the interaction has been deferred, edited, or responded to.

Kord Extensions represents Modals using the ModalForm type. This type provides a container for a Modal's Widgets, settings, and data. Similarly to command arguments classes, you'll need to extend ModalForm when creating your Modals.

class MyModal : ModalForm() { override var title: String = "Test Modal" val line = lineText { label = "Line Text" placeholder = "A single line of text" } val block = paragraphText { label = "Paragraph Text" placeholder = "A block of text which may span multiple lines" } }

The ModalForm type exposes a number of APIs.

Name

Description

lineText

Create a text input Widget which supports a single line of text.

paragraphText

Create a text input Widget which supports multiple lines of text.

The following properties are available on all Widget types.

Name

Type

Description

value

T: Any?

The value provided by the user when the Modal is submitted.

The following properties are available on all text-based Widget types.

Name

Type

Description

id

String

The Widget's ID, used as the component ID on Discord. Defaults to a randomly generated UUID.

🌐

initialValue

String?

An optional value to provide for this Widget. This will show as a pre-filled value on Discord, for the user to edit.

maxLength

Int

The maximum number of characters that a user may provide as a value.

minLength

Int

The minimum number of characters that a user may provide as a value.

🌐

placeholder

String?

Optional placeholder text. This will show as text within the component on Discord, in a lighter font, and will be hidden when the user enters text into the component.

required

Boolean

Whether the user must provide a value for this Widget to submit the Modal. Defaults to true.

translateInitialValue

Boolean

Whether to attempt to translate the initialValue when the Modal is sent to Discord. Defaults to false.

value

String?

The value provided by the user when the Modal is submitted. Will be null when no value was provided.

Name

Description

sendAndAwait

Convenience function to send the Modal to the current interaction, wait for its completion, and call the provided callback block.

The callback block will receive a ModalSubmitInteraction as its first parameter if the Modal was submitted on time, or null otherwise.

Several overrides are provided, which will fill the function's parameters using the given event or command context object.

Name

Description

sendAndDeferEphemeral

Calls sendAndAwait, returning a deferred ephemeral interaction response if the Modal was submitted on time, or null otherwise.

sendAndDeferPublic

Calls sendAndAwait, returning a deferred public interaction response if the Modal was submitted on time, or null otherwise.

Name

Type

Description

🏷️

bundle

String?

Override this to provide a translation bundle to use to translate this Modal's title and its Widgets' labels, initial values, and placeholders.

id

String

Modal ID, used to associate the ModalForm with the corresponding interaction events. This is a randomly generated UUID by default.

timeout

Duration

How long to wait before assuming that the Modal was closed by the user. Defaults to 15 minutes.

🌐

title

String

Required: Override this to set the Modal's title, which will be shown in the Discord client.

Implementation Strategies

There are several ways to support Modals in your bots. We've detailed the major approaches below.

Automatic

The simplest way to add a Modal to your bot is to create a ModalForm as described above, and pass the constructor to your Application Command or Component builder functions.

This causes a Modal to be sent as the first response to that command invocation or component interaction, with the filled-in ModalForm (or null if timed out) provided as an argument to the action block.

publicButton(::MyModal) { // Button configuration action { modal -> // Button body } }

For Slash Commands, you may also combine this with an Arguments subtype.

publicSlashCommand(::MyArguments, ::MyModal) { // Command configuration action { modal -> // Command body } }

Semi-Automatic

Sometimes, the fully automatic approach isn't appropriate. For example, perhaps your Modal needs to be modified based on the arguments provided to the command, or needs to be pre-filled with data from your database.

For these cases, you can instantiate your ModalForm subtype yourself, using the types provided by the Unsafe Module to delay the interaction response.

This approach should be combined with the use of the sendAndDefer functions.

unsafeSlashCommand { // Command configuration action { val modal = MyModal() val result = modal.sendAndDeferEphemeral(this) if (result == null) { // Modal timed out } else { // Modal was submitted, make use of it } } }

This approach is also appropriate for relevant event handlers.

event<ButtonInteractionCreateEvent> { // Event handler configuration action { val modal = MyModal() val result = modal.sendAndDeferEphemeral(this) if (result == null) { // Modal timed out } else { // Modal was submitted, make use of it } } }

Manual

For greater control than either of the above approaches, you can also retain control over how your bot responds to the Modal submission interaction.

You can do this by following the semi-automatic approach, replacing the sendAndDefer functions with sendAndAwait.

unsafeSlashCommand { // Command configuration action { val modal = MyModal() val result = modal.sendAndAwait(this) { interaction -> // interaction will be `null` if the Modal timed out interaction?.deferEphemeralResponse() } if (result == null) { // Modal timed out } else { // Modal was submitted, make use of it } } }

Without Forms

While there are very few situations where this is useful, it is possible to define and handle Modals without using Kord Extensions' abstractions. This is a relatively complex process in comparison, but it's still an approachable strategy.

interaction.modal("title", "modalId") { actionRow { textInput( TextInputStyle.Short, "componentId", "label" ) { allowedLength = 0 ... 1000 placeholder = "Placeholder Text" required = true value = "Initial Value" } } } // ... event<ModalSubmitInteractionCreateEvent> { check { failIfNot(event.interaction.modalId == "modalId") } action { // Value is null if empty/missing/wrong ID val value = event.interaction.textInputs["componentId"]?.value // Event handler body } }
Last modified: 03 September 2024