npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@avion-block/discord-modals

v1.0.0

Published

Utilities and builders for creating and handling Discord modals in discord.js v14.

Readme

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