Handlers
Handlers are the core of Honocord. They allow you to define both the metadata for Discord (like command names, descriptions, and options) and the logic that should run when an interaction occurs.
Honocord provides specialized handler classes for different types of interactions.
Slash Commands
Section titled “Slash Commands”SlashCommandHandler is used for chat input commands. It extends the @discordjs/builders SlashCommandBuilder, so you can use all the familiar methods to define your command’s structure.
import { SlashCommandHandler } from "honocord";
const pingCommand = new SlashCommandHandler() .setName("ping") .setDescription("Replies with Pong!") .addHandler(async (ctx) => { await ctx.reply("Pong!"); });Options and Subcommands
Section titled “Options and Subcommands”Since it extends SlashCommandBuilder, you can add options and subcommands directly.
const userCommand = new SlashCommandHandler() .setName("userinfo") .setDescription("Get info about a user") .addUserOption((option) => option.setName("target").setDescription("The user to lookup").setRequired(true)) .addHandler(async (ctx) => { const user = ctx.options.getUser("target", true); // if required option, pass true as second argument! await ctx.reply(`User: ${user.username} (${user.id})`); });Autocomplete
Section titled “Autocomplete”You can handle autocomplete interactions by adding an autocomplete handler to your command.
const searchCommand = new SlashCommandHandler() .setName("search") .setDescription("Search something") .addStringOption((option) => option.setName("query").setDescription("The search query").setAutocomplete(true)) .addAutocompleteHandler(async (ctx) => { const focused = ctx.options.getFocused(); const results = ["apple", "banana", "cherry"].filter((s) => s.startsWith(focused.value));
await ctx.respond(results.map((r) => ({ name: r, value: r }))); });Autocomplete Helper
Section titled “Autocomplete Helper”Honocord provides an AutocompleteHelper utility to simplify filtering choices based on user input.
import { AutocompleteHelper } from "honocord";
command.addAutocompleteHandler(async (ctx) => { const focused = ctx.options.getFocused()!;
const helper = new AutocompleteHelper(focused.value).addChoices( { name: "Apple", value: "apple" }, { name: "Banana", value: "banana" }, { name: "Cherry", value: "cherry" } );
// Automatically filters choices that start with the user's input await ctx.respond(helper.response(["name"]));});Context Menu Commands
Section titled “Context Menu Commands”Use ContextCommandHandler to create User or Message context menu commands.
import { ContextCommandHandler, ContextCommandType } from "honocord";
const userContext = new ContextCommandHandler(ContextCommandType.User).setName("Get ID").addHandler(async (ctx) => { await ctx.reply(`The user's ID is ${ctx.targetId}`);});Message Components
Section titled “Message Components”ComponentHandler manages interactions from buttons and select menus. It uses a prefix-based matching system for custom_ids.
import { ComponentHandler } from "honocord";import { ComponentType } from "discord-api-types/v10";
// Matches any button with a custom_id starting with "vote:"const voteButton = new ComponentHandler("vote", ComponentType.Button).addHandler(async (ctx) => { const [_, candidate] = ctx.customId.split(":"); await ctx.reply(`You voted for ${candidate}!`);});Select Menus
Section titled “Select Menus”You can create handlers for all types of select menus by specifying the correct ComponentType.
const menuHandler = new ComponentHandler("select_role", ComponentType.RoleSelect).addHandler(async (ctx) => { // ctx is typed specifically for RoleSelect interactions const roles = ctx.roles; await ctx.reply(`Selected ${roles.size} roles.`);});Modals
Section titled “Modals”ModalHandler works similarly to component handlers, matching the custom_id of a submitted modal.
import { ModalHandler } from "honocord";
const feedbackModal = new ModalHandler("feedback_form").addHandler(async (ctx) => { const feedback = ctx.components.getTextInputValue("feedback_input"); await ctx.reply("Thank you for your feedback!");});Guild-Specific Commands
Section titled “Guild-Specific Commands”By default, commands are registered globally. You can restrict a command to specific guilds using .setGuildIds().
const adminCommand = new SlashCommandHandler() .setName("admin") .setDescription("Restricted command") .setGuildIds(["123456789012345678"]) // Only available in this guild .addHandler(async (ctx) => { await ctx.reply("Welcome, Admin."); });Loading Handlers
Section titled “Loading Handlers”Finally, you must load your handlers into the Honocord instance for them to be processed.
import { Honocord } from "honocord";
const bot = new Honocord();
bot.loadHandlers(pingCommand, userCommand, voteButton, feedbackModal);Or if you have many handlers, you can load them from an array:
import * as handlers from "./handlers"; // assuming handlers/index.ts exports all (and ONLY) handlers
bot.loadHandlers(...Object.values(handlers));Webhook Events
Section titled “Webhook Events”WebhookEventHandler manages Discord webhook events. Unlike interactions, webhooks are one-way notifications from Discord about events happening in your app.
Standard Mode (Default)
Section titled “Standard Mode (Default)”In standard mode, handlers must return a Response object. This is ideal for non-Cloudflare Workers environments.
import { WebhookEventHandler } from "honocord";import { ApplicationWebhookEventType } from "discord-api-types/v10";
const messageHandler = new WebhookEventHandler(ApplicationWebhookEventType.LobbyMessageCreate);
messageHandler.addHandler(async (c) => { const message = c.var.data; console.log(`New message from ${message.author.username}: ${message.content}`);
// Must return a Response in standard mode return c.json({ received: true });});Worker Mode (Cloudflare Workers)
Section titled “Worker Mode (Cloudflare Workers)”For Cloudflare Workers, set the second constructor parameter to true. In worker mode:
- No return value is required from your handler
- Honocord automatically responds with
200 OKto Discord - Processing happens asynchronously via
waitUntil, extending the worker’s lifetime fetch()andgetApp()methods are disabled (type error + runtime error)
// Enable worker mode with second parameterconst messageHandler = new WebhookEventHandler( ApplicationWebhookEventType.LobbyMessageCreate, true // Worker mode);
messageHandler.addHandler(async (c) => { const message = c.var.data; console.log(`Processing message: ${message.content}`);
// No return needed - Honocord handles the response // Your code runs asynchronously via waitUntil});Webhook vs Interactions
Section titled “Webhook vs Interactions”| Feature | Interactions | Webhooks |
|---|---|---|
| Trigger | User actions (slash commands, buttons) | Discord events (entitlements, lobby messages) |
| Response | Required within 3 seconds | Must respond within 3 seconds (204 or 200) |
| Reply Method | interaction.reply() | Use REST API separately |
| Route | / or /interactions | /webhook |
| Setup | Interactions Endpoint URL | Webhook Events URL in Discord Developer Portal |
| CF Workers | Async via waitUntil | Async via waitUntil (worker mode) |
Webhook Handler Setup
Section titled “Webhook Handler Setup”When using WebhookEventHandler with Honocord:
- Load handlers using
bot.loadHandlers() - The webhook endpoint is automatically mounted at
/webhook - Configure your Discord webhook URL to point to
https://your-domain.com/webhook
const bot = new Honocord({ isCFWorker: true }); // Enable CF Workers mode
bot.loadHandlers(messageHandler);
// Webhook available at /webhook// On CF Workers: responds immediately with 200, processes async// On other platforms: waits for handler to return Responseexport default bot.getApp();Standalone Webhook Handlers
Section titled “Standalone Webhook Handlers”Webhook handlers can run independently of Honocord. This is useful for microservices or separate webhook endpoints.
Standard Mode Only: The fetch() and getApp() methods are only available in standard mode (when forWorker is false or omitted).
import { Hono } from "hono";
const handler = new WebhookEventHandler( ApplicationWebhookEventType.EntitlementCreate // false or omit for standard mode);
handler.addHandler(async (c) => { const entitlement = c.var.data; console.log(`New entitlement: ${entitlement.id}`);
// Must return Response in standard mode return c.json({ ok: true });});
// Use as standalone fetch handlerexport default { fetch: handler.fetch.bind(handler),};
// Or mount in a Hono appconst app = new Hono();app.route("/discord", handler.getApp());export default app;Available Webhook Events
Section titled “Available Webhook Events”Discord supports various webhook event types. Common ones include:
APPLICATION_AUTHORIZED- App authorized by userENTITLEMENT_CREATE,ENTITLEMENT_UPDATE,ENTITLEMENT_DELETE- Monetization eventsLOBBY_MESSAGE_CREATE,LOBBY_MESSAGE_UPDATE,LOBBY_MESSAGE_DELETE- Lobby messagesGAME_DIRECT_MESSAGE_CREATE,GAME_DIRECT_MESSAGE_UPDATE,GAME_DIRECT_MESSAGE_DELETE- Game DMs
Refer to the Discord Webhook Events documentation for the complete list.