Collectors
discord.js collectors are powerful but fiddly: you wire an event emitter, set a time, write a filter, remember that dismissed modals need their own timeout, and translate the…
Beyond these, see the pagination and confirm helpers for ready-made paged lists and yes/no gates.
Wait for a message ("type your answer")
ctx.awaitMessageFrom(userId?, options?) waits for the next message in the
current channel from a user (defaults to the invoking user):
await ctx.reply("What's your favourite colour?");
const answer = await ctx.awaitMessageFrom(ctx.user.id, { time: 30_000 });
if (answer === null) return ctx.followUp("Timed out.");
await ctx.followUp(`Nice — ${answer.content}!`);The standalone awaitMessage(channel, options) does the same for any text
channel; options takes { filter, time } (default time 60s).
Wait for a modal submission
ctx.awaitModal(modal, options?) (on command and component contexts) shows a
modal and waits for the submission — scoped to the same user and that modal's
custom-id, always bounded — sidestepping the "Unknown interaction after a
cancelled modal" trap:
import { modal, textInput } from "spearkit";
const form = modal({
id: "feedback",
title: "Feedback",
fields: { text: textInput({ label: "Your feedback", required: true }) },
run: (ctx) => ctx.replyEphemeral("thanks"), // routed fallback
});
const submitted = await ctx.awaitModal(form.build(), { time: 120_000 });
if (submitted === null) return; // dismissed or timed out
await submitted.reply({ content: submitted.fields.getTextInputValue("text"), ephemeral: true });This is the inline alternative to registering a separate modal handler and threading state through its custom-id.
Wait for a component click
awaitComponent(message, options) waits for the next button/select interaction
on a message. options takes { filter, time, componentType }. You must still
acknowledge the returned interaction (update/deferUpdate/reply):
import { awaitComponent } from "spearkit";
const sent = await ctx.channel!.send({ content: "Pick one", components: [row] });
const click = await awaitComponent(sent, { time: 15_000 });
if (click === null) return;
await click.update("Got it!");Contexts
Every spearkit handler — command, button, select, modal — receives a context object. They all share BaseContext, which smooths over discord.js' reply/defer/edit/follow-up state…
Guards
Guards are declarative preconditions that run before a handler. They work uniformly across slash commands, components (buttons, selects, modals), prefix commands and context-menu…