Skip to content

AvionBlock/discord-modals

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

discord-modals (Modal v2 / Label-based)

Lightweight helper library for discord.js v14 to:

  • listen for Modal Submit interactions via the raw gateway event,
  • emit a typed client.on("modalSubmit", ...) event,
  • provide a set of Modal v2 builders (Label-based format),
  • simplify replying / deferring / updating and showing modals.

⚠️ discord.js v14 only.


Features

  • init(client) wires raw INTERACTION_CREATE → emits modalSubmit
  • ModalSubmitInteraction wrapper:
    • fields, selectMenus, and components (supports legacy + new payloads)
    • helpers: getTextInputValue, getField, getSelectMenuValues, getSelectMenu
    • response helpers mixin: reply, deferReply, editReply, followUp, update, showModal
  • ✅ Modal builders:
    • Modal
    • ModalLabel
    • TextInputComponent
    • SelectMenuComponent
  • 🧩 Backward-compat:
    • accepts raw/legacy modal components and tries to normalize to Label
    • ModalActionRow is kept for compatibility, but deprecated

Install

npm i @avion-block/discord-modals

Peer deps:

  • discord.js v14

Quick start

1) Initialize once

import { Client, GatewayIntentBits } from "discord.js";
import initModals from "@avion-block/discord-modals";

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

// Attach the raw interaction listener
initModals(client);

client.login(process.env.DISCORD_TOKEN);

2) Listen to modalSubmit

client.on("modalSubmit", async (modal) => {
  const name = modal.getTextInputValue("name");
  const roleIds = modal.getSelectMenuValues("roles") ?? [];

  await modal.reply({
    content: `Name: ${name ?? "(empty)"}\nRoles: ${roleIds.join(", ") || "(none)"}`,
    ephemeral: true,
  });
});

Showing a modal

You can show a modal using:

  • modal.showModal(modalBuilderOrJson) (available on the interaction wrapper)
  • or showModal(modal, { client, interaction })

Using the builder

import {
  Modal,
  ModalLabel,
  TextInputComponent,
} from "@avion-block/discord-modals";

client.on("interactionCreate", async (interaction) => {
  if (!interaction.isChatInputCommand()) return;
  if (interaction.commandName !== "form") return;

  const modal = new Modal()
    .setCustomId("my_form")
    .setTitle("Example form")
    .addLabelComponents(
      new ModalLabel().setLabel("Your name").setComponent(
        new TextInputComponent()
          .setCustomId("name")
          .setLabel("Name") // used for inference / compatibility
          .setPlaceholder("Type your name...")
          .setRequired(true),
      ),
    );

  // If you already use this library's InteractionResponses mixin, you can:
  // await (interaction as any).showModal(modal);
  //
  // Otherwise use the exported helper:
  const { showModal } = await import("@avion-block/discord-modals");
  await showModal(modal, { client: interaction.client, interaction });
});

Using raw JSON

import { showModal } from "@avion-block/discord-modals";

await showModal(
  {
    title: "Raw modal",
    custom_id: "raw_modal",
    components: [
      {
        type: 18,
        label: "Email",
        component: {
          type: 4,
          custom_id: "email",
          label: "Email",
          style: 1,
          required: true,
        },
      },
    ],
  },
  { client, interaction },
);

Builders

Modal

const modal = new Modal()
  .setCustomId("settings")
  .setTitle("Settings")
  .addComponents(
    // You can pass raw components: they’ll be wrapped into ModalLabel automatically.
    new TextInputComponent().setCustomId("bio").setLabel("Bio"),
  );

Notes:

  • Internally Modal.components stores ModalLabel[] (Modal v2 format).
  • If you add a raw component, the library wraps it in a label and tries to infer label text from component.label or component.placeholder. If it can’t infer, it falls back to "Field".

ModalLabel

const label = new ModalLabel()
  .setLabel("Username")
  .setDescription("This will be displayed publicly.")
  .setComponent(
    new TextInputComponent().setCustomId("username").setLabel("Username"),
  );

TextInputComponent

import { TextInputStyle } from "discord-api-types/v10";

const input = new TextInputComponent()
  .setCustomId("feedback")
  .setLabel("Feedback")
  .setStyle(TextInputStyle.Paragraph) // or "LONG" / 2
  .setMinLength(10)
  .setMaxLength(500)
  .setPlaceholder("Write your feedback...")
  .setRequired(true);

SelectMenuComponent (StringSelect)

const select = new SelectMenuComponent()
  .setCustomId("color")
  .setPlaceholder("Pick a color")
  .setMinValues(1)
  .setMaxValues(1)
  .addOptions(
    { label: "Red", value: "red" },
    { label: "Green", value: "green" },
  );

Tip: for label inference on selects, make sure to set placeholder (or set label explicitly).


Handling submitted values

Inside client.on("modalSubmit", modal => ...):

Text inputs

const value = modal.getTextInputValue("name"); // string | null
const field = modal.getField("name"); // ModalSubmitField | null

Select menus

const values = modal.getSelectMenuValues("roles"); // string[] | null
const menu = modal.getSelectMenu("roles"); // ModalSubmitSelectMenu | null

Where values are stored

  • modal.fields: ModalSubmitField[] (type 4 components)
  • modal.selectMenus: ModalSubmitSelectMenu[] (select components)
  • modal.components: (ModalLabel | any)[]
    • wraps Label (type 18) into ModalLabel when possible
    • keeps unknown/legacy structures as raw any

Interaction response helpers (Mixin)

ModalSubmitInteraction includes response helpers:

await modal.deferReply({ ephemeral: true });
await modal.editReply({ content: "Updated!" });
await modal.followUp({ content: "Follow up message" });

Also supports:

  • reply, fetchReply, deleteReply
  • deferUpdate, update (for message-component interactions)

Deprecated: ModalActionRow

Discord Modal v2 uses Label (type 18), not ActionRow.
ModalActionRow exists only for backward compatibility.

import { ModalActionRow } from "@avion-block/discord-modals";

/\*\*

- @deprecated Prefer ModalLabel + component builders.
  \*/
  const row = new ModalActionRow().addComponent(
  new TextInputComponent().setCustomId("x").setLabel("X"),
  );

Utilities

  • Util.verifyString(...)
  • Util.parseEmoji(...)
  • Util.resolvePartialEmoji(...)
  • SnowflakeUtil.generate(...), SnowflakeUtil.timestampFrom(...), etc.

TypeScript typings

The library augments discord.js Client with:

client.on("modalSubmit", (modal) => { ... });

License

MIT

About

A library for creating modal forms in Discord

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors