Skip to content

Interactions

Lunibee provides typed interaction classes for every Discord interaction type. All interactions share a common base with lifecycle methods for replying, deferring, and following up.

import {
CommandInteraction,
ComponentInteraction,
ModalSubmitInteraction,
AutocompleteInteraction,
Interaction,
} from "lunibee";
Property Type Description
id string Interaction snowflake ID.
applicationId string The application’s ID.
token string Interaction token (valid for 15 minutes).
guildId string | undefined Guild ID if the interaction happened in a server.
channelId string | undefined Channel ID.
user raw user data The user who triggered the interaction.
member GuildMember | undefined The guild member who triggered it (if in a guild).
Method Description
reply(options) Sends the initial response. Accepts content, embeds, components, ephemeral.
deferReply(options?) Acknowledges immediately, showing a loading state.
editReply(options) Edits the initial response (after reply or deferReply).
deleteReply() Deletes the initial response.
followUp(options) Sends a follow-up message.

Use type guard methods on the base Interaction to narrow to the correct subtype:

client.on("interactionCreate", async (interaction) => {
if (interaction.isChatInputCommand()) {
// CommandInteraction
} else if (interaction.isComponent()) {
// ComponentInteraction
} else if (interaction.isModalSubmit()) {
// ModalSubmitInteraction
} else if (interaction.isAutocomplete()) {
// AutocompleteInteraction
}
});

Triggered when a user runs a slash command (/commandname).

Property Type Description
commandName string Name of the slash command.
options CommandOptions Typed option resolver.
client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const { commandName, options } = interaction;
if (commandName === "ban") {
const target = options.getUser("target", true);
const reason = options.getString("reason") ?? "No reason provided";
// ...
}
});
Method Returns Description
getString(name, required?) string | null Gets a string option.
getInteger(name, required?) number | null Gets an integer option.
getNumber(name, required?) number | null Gets a float option.
getBoolean(name, required?) boolean | null Gets a boolean option.
getUser(name, required?) object | null Gets the resolved user data.
getChannel(name, required?) object | null Gets the resolved channel data.
getRole(name, required?) object | null Gets the resolved role data.
getAttachment(name, required?) object | null Gets the attachment data.
getSubcommand(required?) string | null Gets the subcommand name.
getSubcommandGroup(required?) string | null Gets the subcommand group name.

When required = true, the method throws a TypeError if the option is missing.

client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === "userinfo") {
await interaction.deferReply();
const user = interaction.options.getUser("target") ?? interaction.user;
const embed = new EmbedBuilder()
.setTitle(`User Info: ${(user as any).username}`)
.addFields({ name: "ID", value: (user as any).id });
await interaction.editReply({ embeds: [embed] });
}
});

ComponentInteraction — Buttons & Select Menus

Section titled “ComponentInteraction — Buttons & Select Menus”

Triggered when a user clicks a button or chooses a select menu option.

Property Type Description
customId string The customId set on the component.
componentType number Component type (2 = Button, 3 = String Select, etc.).
values string[] Selected values (only for select menus).
message Message The message containing the component.
Method Description
update(options) Updates the originating message instead of sending a new reply.
deferUpdate() Defers without showing a loading state (for components).
client.on("interactionCreate", async (interaction) => {
if (!interaction.isComponent()) return;
if (interaction.customId === "btn_confirm") {
await interaction.update({ content: "✅ Confirmed!", components: [] });
}
if (interaction.customId === "select_color") {
const colors = interaction.values; // e.g. ["red", "blue"]
await interaction.reply({ content: `You selected: ${colors.join(", ")}`, ephemeral: true });
}
});

ModalSubmitInteraction — Modal Submissions

Section titled “ModalSubmitInteraction — Modal Submissions”

Triggered when a user submits a modal form.

Property Type Description
customId string The customId set on the modal.
fields ModalFields Accessor for submitted text input values.
const value = interaction.fields.getTextInputValue("field_custom_id");
client.on("interactionCreate", async (interaction) => {
if (!interaction.isModalSubmit()) return;
if (interaction.customId === "feedback_modal") {
const subject = interaction.fields.getTextInputValue("subject");
const body = interaction.fields.getTextInputValue("body");
await interaction.reply({
content: `📨 Feedback received!\n**${subject}**\n${body}`,
ephemeral: true,
});
}
});

AutocompleteInteraction — Live Suggestions

Section titled “AutocompleteInteraction — Live Suggestions”

Triggered as a user types in a slash command option with autocomplete: true.

[!IMPORTANT] Autocomplete interactions cannot use reply(). Use respond() to send choices.

Property Type Description
commandName string The slash command name.
options CommandOptions Resolved options (use getFocused for the active field).

Sends up to 25 autocomplete choice objects:

await interaction.respond([
{ name: "Option A", value: "a" },
{ name: "Option B", value: "b" },
]);
client.on("interactionCreate", async (interaction) => {
if (!interaction.isAutocomplete()) return;
const focused = interaction.options.getFocused(true);
if (focused.name === "tag") {
const tags = ["typescript", "javascript", "bun", "discord"];
const filtered = tags.filter(t => t.startsWith(focused.value.toLowerCase()));
await interaction.respond(filtered.map(t => ({ name: t, value: t })));
}
});

Any non-autocomplete interaction can show a modal as its initial response:

client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand() || interaction.commandName !== "report") return;
const modal = new ModalBuilder()
.setCustomId("report_modal")
.setTitle("Report a User")
.addComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(
new TextInputBuilder()
.setCustomId("reason")
.setLabel("Reason")
.setStyle(TextInputStyle.Paragraph)
.setRequired(true),
),
);
await interaction.showModal(modal);
});