The interaction model
Most of DiscordKit is built around one idea:
Discord sends interactions, and your application routes them to handlers.
Commands are only one kind of interaction.
DiscordKit uses the same application model for commands, components, modal submissions, and autocomplete.
The complete flow
Section titled “The complete flow”At a high level:
Discord │ │ Interaction ▼discordgo.Session │ ▼Router.Handle │ ▼Context │ ▼Route resolution │ ▼Global middleware │ ▼Route middleware │ ▼Handler │ ▼Response │ ▼DiscordUnderstanding each stage explains most of the framework.
Discord sends an interaction
Section titled “Discord sends an interaction”Interactions are generated by actions such as:
- executing a slash command;
- using a user context command;
- using a message context command;
- clicking a button;
- choosing an item from a select menu;
- submitting a modal;
- typing into an autocomplete-enabled command option.
discordgo receives those interactions from Discord.
DiscordKit does not replace that connection.
Router.Handle is a discordgo handler
Section titled “Router.Handle is a discordgo handler”A DiscordKit Router is connected to discordgo like this:
session.AddHandler(router.Handle)Router.Handle is responsible for adapting the incoming discordgo interaction into the DiscordKit application model.
It creates a Context and dispatches it through the Router.
Context represents one interaction
Section titled “Context represents one interaction”Every DiscordKit handler receives:
*discordkit.ContextA Context exists for exactly one interaction.
It gives the handler access to:
- the underlying
*discordgo.Session; - the original interaction;
- command options;
- resolved users, roles, channels, members, and attachments;
- route parameters;
- form submissions;
- response helpers;
- a standard Go
context.Context.
For example:
func greet(c *discordkit.Context) error { user, err := c.RequireUserOption("user") if err != nil { return err }
return c.ReplyText("Hello, " + user.Username)}Route resolution depends on the interaction type
Section titled “Route resolution depends on the interaction type”DiscordKit supports four route categories.
Commands
Section titled “Commands”router.Command("ping", pingHandler)A command with subcommands uses its full command path:
router.Command("admin ban", banHandler)Components
Section titled “Components”Components are routed through their custom_id:
router.Component( "/jobs/:jobID/save", saveJobHandler,)Named route parameters become available through Context:
jobID := c.MustParam("jobID")Modal submissions
Section titled “Modal submissions”Modal submissions use the same pattern-based routing model:
router.Modal( "/jobs/:jobID/edit", editJobHandler,)Autocomplete
Section titled “Autocomplete”Autocomplete routes combine the command path with the focused option:
router.Autocomplete( "search", "query", searchAutocomplete,)Middleware wraps handlers
Section titled “Middleware wraps handlers”Middleware can run behavior before or after application handlers.
DiscordKit middleware follows the standard Go wrapping model:
type Middleware func(Handler) HandlerFor example:
router := discordkit.NewRouter( discordkit.Logging(nil), discordkit.RequireGuild(),)Global middleware is applied to all matched routes.
A route can also have additional middleware:
router.Command( "admin", adminHandler, discordkit.RequirePermissions( discordgo.PermissionAdministrator, ),)Recovery is always applied
Section titled “Recovery is always applied”The Router automatically applies DiscordKit’s recovery middleware around dispatch.
If a handler panics, the panic becomes a *discordkit.PanicError instead of escaping through the interaction handler.
Recovery does not automatically send an error message to the user.
Applications remain responsible for deciding how user-facing errors should be presented.
Handlers return errors
Section titled “Handlers return errors”A Handler is:
type Handler func(*Context) errorReturning errors instead of handling every failure locally allows errors to propagate through middleware and eventually reach the Router error hook.
For example:
router.OnError(func(c *discordkit.Context, err error) { slog.Error("interaction failed", "error", err)})If no custom hook is installed, DiscordKit logs the error using slog.
Responses acknowledge interactions
Section titled “Responses acknowledge interactions”Discord interactions must be acknowledged according to Discord’s interaction rules.
A handler may send an initial response:
return c.ReplyText("Done")or defer work:
if err := c.Defer(false); err != nil { return err}
// perform work...
_, err := c.Edit( discordkit.MessageSpec{ Content: "Done", },)return errDiscordKit tracks this lifecycle to prevent invalid sequences such as sending two initial responses.
One Router, several interaction types
Section titled “One Router, several interaction types”A larger application can register different interaction types in the same Router:
router := discordkit.NewRouter( discordkit.Logging(nil),)
router.Command( "jobs search", searchJobs,)
router.Component( "/jobs/:jobID/save", saveJob,)
router.Modal( "/jobs/:jobID/edit", editJob,)
router.Autocomplete( "jobs search", "query", autocompleteJobs,)These routes use the same Context, Handler, middleware, error handling, and response model.
That consistency is one of DiscordKit’s central design goals.
The mental model
Section titled “The mental model”A useful way to think about DiscordKit is:
Discord interaction +routing information +application middleware =handler executionThe framework does not attempt to hide Discord.
Instead, it provides a structured path from an incoming Discord interaction to application code.
Continue with Router to learn how route registration, groups, parameters, conflicts, and middleware composition work.