Group commands, components, and modals into modular routers. Each router is a self-contained unit that you can compose, test, and reuse across applications.
Define slash commands with full type-safe option handling, autocomplete, and subcommand support.
import { command, CommandRouter } from "@theminesastudios/mini-interaction";
import { ApplicationCommandOptionType } from "discord-api-types/v10";
export const greetCommand = command({
name: "greet",
description: "Greet a user",
options: [
{
type: ApplicationCommandOptionType.User,
name: "target",
description: "Who to greet",
required: true,
},
{
type: ApplicationCommandOptionType.String,
name: "message",
description: "Custom message",
},
],
async handler(ctx) {
const target = ctx.getOption("target");
const msg = ctx.getOption("message") ?? "Hello!";
return ctx.respond({ content: `<@${target}> ${msg}` });
},
});
// Compose into a router module
export const funRouter = new CommandRouter()
.add(greetCommand)
.add(pingCommand)
.add(rollCommand);import { subcommand, subcommandGroup } from "@theminesastudios/mini-interaction";
const configShow = subcommand({
name: "show",
parent: "config",
handler: async (ctx) => ctx.respond({ content: "Config display" }),
});
const configSet = subcommand({
name: "set",
parent: "config",
options: [{ type: 3, name: "key", description: "Setting key" }],
handler: async (ctx) => ctx.respond({ content: "Config updated" }),
});
export const configRouter = new CommandRouter()
.add(subcommandGroup("config", "Manage configuration", [configShow, configSet]));Route button clicks, select menu selections, and other component interactions by custom ID pattern matching.
import { component, ComponentRouter } from "@theminesastudios/mini-interaction";
import { ButtonStyle, ComponentType } from "discord-api-types/v10";
const ticketCreate = component({
// Custom ID with captured parameter
pattern: /^ticket:(?<type>support|billing)$/,
async handler(ctx) {
const { type } = ctx.params;
return ctx.respond({
type: MessageComponentType.ChannelMessageWithSource,
data: {
content: `Creating ${type} ticket...`,
flags: MessageFlags.Ephemeral,
},
});
},
});
const ticketClose = component({
pattern: /^ticket:close:(?<id>\d+)$/,
async handler(ctx) {
const { id } = ctx.params;
await ctx.deferUpdate();
// ... close ticket logic
},
});
export const ticketRouter = new ComponentRouter()
.add(ticketCreate)
.add(ticketClose);Handle modal submissions with type-safe field extraction and validation.
import { modal, ModalRouter } from "@theminesastudios/mini-interaction";
const feedbackModal = modal({
customId: "feedback_submit",
async handler(ctx) {
const name = ctx.getField("feedback_name");
const rating = ctx.getField("feedback_rating");
const comment = ctx.getField("feedback_comment");
await ctx.respond({
content: `Thanks, ${name}! Your feedback has been recorded.`,
flags: MessageFlags.Ephemeral,
});
},
});
export const feedbackRouter = new ModalRouter()
.add(feedbackModal);Compose multiple routers into your application. Each router registers its handlers with the framework's middleware pipeline.
import { createApp } from "@theminesastudios/mini-interaction";
import { funRouter } from "./routers/fun";
import { ticketRouter } from "./routers/tickets";
import { feedbackRouter } from "./routers/feedback";
import { configRouter } from "./routers/config";
const app = createApp({
publicKey: process.env.DISCORD_PUBLIC_KEY!,
applicationId: process.env.DISCORD_CLIENT_ID!,
});
app.use(funRouter);
app.use(ticketRouter);
app.use(feedbackRouter);
app.use(configRouter);
export const POST = app.handle();Attach middleware to individual routers for scoped logic like permission checks, rate limiting, or logging.
import { command, CommandRouter, type Middleware } from "@theminesastudios/mini-interaction";
const requireMod: Middleware = async (ctx, next) => {
const member = ctx.interaction.member;
if (!member?.permissions?.includes("ModerateMembers")) {
return ctx.respond({
content: "You need the Moderate Members permission.",
flags: MessageFlags.Ephemeral,
});
}
return next(ctx);
};
export const modRouter = new CommandRouter()
.use(requireMod)
.add(warnCommand)
.add(muteCommand)
.add(kickCommand);publicKeyrequiredstringDiscord application public key for interaction verification
applicationIdrequiredstringDiscord application client ID
onError(err) => ResponseGlobal error handler for uncaught router errors
loggerLoggerCustom logger instance (defaults to console)