hono-discord-interactions
v1.0.1
Published
A Discord interactions handler built with Bun and Hono. Useful for handling slash commands, buttons, and other interactions in a performant way for Interactions Endpoints URL bots.
Readme
hono-discord-interactions
[!WARNING] This package is currently under active development. Breaking changes can happen at any time, including API changes and behavior changes. Do not rely on this package in production without pinning an exact version and testing each update.
TypeScript handler for Discord Interactions Endpoint URL bots, built with Hono.
It provides:
- a
POST /api/interactionsroute with Ed25519 signature verification - a strongly typed interaction event bus
- a Discord.js-like interaction facade (
reply,deferReply,isButton, etc.)
Features
- Signature verification using Discord headers:
x-signature-ed25519x-signature-timestamp
- Supported interaction types:
PingApplicationCommandApplicationCommandAutocompleteMessageComponent(buttons and select menus)ModalSubmit
- Typed events for listener autocomplete:
interactionautocompleteslashCommandmessageComponentbuttonselectMenumodalSubmit
- Automatic Discord callback response handling in the HTTP route
Runtime Compatibility
This package is designed for fetch-compatible runtimes.
- Bun: supported
- Node.js: supported (with a fetch/server adapter)
- Vercel (Node runtime): supported
- Vercel (Edge runtime): supported
- Cloudflare Workers: supported (enable Node.js compatibility because
eventsis used)
Cloudflare wrangler.toml example:
name = "hono-discord-interactions"
main = "src/worker.ts"
compatibility_date = "2025-01-01"
compatibility_flags = ["nodejs_compat"]Installation
npm i hono-discord-interactionsConfiguration
Required environment variable:
PUBLIC_KEY: your Discord application public key
You can provide it from:
process.env.PUBLIC_KEY(Node, Bun, Vercel)c.env.PUBLIC_KEY(platform bindings like Workers)
Public API
Default import:
import interactionsApi from "hono-discord-interactions";
const { app, client } = interactionsApi;app: Hono app withPOST /api/interactionsclient:InteractionHandlerinstance for typed listeners
Quick Start
import interactionsApi from "hono-discord-interactions";
interactionsApi.client.on("slashCommand", async (interaction) => {
if (!interaction.isChatInputCommand()) {
await interaction.deferReply({ ephemeral: true });
return;
}
if (interaction.commandName === "ping") {
await interaction.reply({ content: "Pong" });
return;
}
await interaction.reply({ content: "Unknown command" });
});Set your Discord Interactions Endpoint URL to:
https://your-domain.tld/api/interactionsDeployment Examples
Bun
import interactionsApi from "hono-discord-interactions";
Bun.serve({
port: Number(process.env.PORT ?? 3000),
fetch: interactionsApi.app.fetch,
});Node.js
import interactionsApi from "hono-discord-interactions";
export default interactionsApi.app;Vercel Edge Function
import interactionsApi from "hono-discord-interactions";
export const runtime = "edge";
export default interactionsApi.app.fetch;Vercel Node Function
import interactionsApi from "hono-discord-interactions";
export const runtime = "nodejs";
export default interactionsApi.app.fetch;Cloudflare Worker
import interactionsApi from "hono-discord-interactions";
export default {
fetch: interactionsApi.app.fetch,
};Typed Event API
InteractionHandler exposes typed on, once, and off methods.
import interactionsApi from "hono-discord-interactions";
interactionsApi.client.on("button", async (interaction) => {
if (interaction.customId === "approve") {
await interaction.update({ content: "Approved." });
}
});
interactionsApi.client.on("selectMenu", async (interaction) => {
await interaction.reply({
content: `Selected: ${interaction.values.join(", ")}`,
flags: 64,
});
});
interactionsApi.client.on("autocomplete", async (interaction) => {
await interaction.respond([
{ name: "JavaScript", value: "js" },
{ name: "TypeScript", value: "ts" },
]);
});
interactionsApi.client.on("modalSubmit", async (interaction) => {
await interaction.reply({ content: "Modal submitted." });
});Interaction Facade
Discord.js-like helpers include:
- Type guards:
isCommand,isAutocomplete,isButton,isAnySelectMenu,isModalSubmit, and more - Response builders:
reply-> callback type4deferReply-> callback type5deferUpdate-> callback type6update-> callback type7respond-> callback type8showModal-> callback type9
Normalized fields:
guildId,channelId,applicationId,userIdcommandName,customId,componentType,valuescreatedTimestamp,createdAt
Project Structure
src/
index.ts
handler.ts
routes/
interactions.ts
types/
interaction.ts
utils/
discordUtils.ts
interactionFacade.ts
supabaseClient.tsScripts
# typecheck
npm run typecheck
# static checks (Biome)
npm run check
# lint
npm run lint
# format
npm run format
# tests
npm run testNotes
- This package targets Discord Interactions Endpoint URL flow (HTTP webhooks), not gateway bot event streams.
- Safe default callback behavior is used when no listener responds:
- autocomplete -> empty choices
- slash/modal -> deferred reply
- message component -> deferred update
