One command, two ways in
Share a callback between message and slash commands.
Use CommandType.BOTH when the same idea should work as /ping and !ping. The callback receives a union: an invocation has either interaction or message, so branch on that before using flow-specific data.
import { CommandType } from "swagcommands";
import type { CommandObject } from "swagcommands";
const ping = {
type: CommandType.BOTH,
description: "Check whether the bot is responsive",
aliases: ["p"],
reply: true,
callback: ({ interaction, message }) =>
interaction ? "Slash pong!" : `Pong for ${message.author.username}!`,
} satisfies CommandObject;
export default ping;The slash side needs deployment; the message side uses the prefix immediately. aliases apply only to messages. description and Discord option metadata are for slash deployment. The same guards run for both flows, although flow-specific precondition classes can implement different methods.
For a more involved command, move shared work into a normal function and keep the callback's branch small. Discord option values and message tokens rarely have exactly the same shape, so normalize them at the edge.