Plugins
A plugin is a named, reusable bundle of commands, events and components. It lets you package a feature once and install it into any SpearClient with a single call — useful for…
Defining a plugin
definePlugin is an identity helper: it returns the object you pass it, but gives
it the SpearPlugin type and editor hints.
interface SpearPlugin {
name: string;
setup(client: SpearClient): Awaitable<void>;
}A plugin has a name and a setup function. setup receives the client and
registers whatever the feature needs — commands, events, components — typically
via client.register.
import { definePlugin, button, command, event, option, row } from "spearkit";
export const moderation = definePlugin({
name: "moderation",
setup(client) {
const confirmKick = button({
id: "kick:{userId}",
label: "Confirm kick",
style: "Danger",
run: (ctx) => ctx.update(`Kicked <@${ctx.params.userId}> (demo).`), // userId: string
});
const warn = command({
name: "warn",
description: "Warn a member",
options: {
member: option.user({ description: "Member", required: true }),
reason: option.string({ description: "Reason" }),
},
run: (ctx) =>
ctx.reply({
// member: User, reason: string | undefined
content: `Warning ${ctx.options.member.tag}: ${ctx.options.reason ?? "no reason given"}`,
components: [row(confirmKick.build({ userId: ctx.options.member.id }))],
ephemeral: true,
}),
});
const ready = event("clientReady", (c) => console.log(`[moderation] ready on ${c.user.tag}`));
client.register(warn, confirmKick, ready);
},
});Everything declared inside setup is local to the plugin; only what you pass to
client.register becomes active on the client.
Installing a plugin
Install one or more plugins with client.use. It runs each plugin's setup in
order and resolves to the client, so you can chain it with the rest of startup.
import { SpearClient, Intents } from "spearkit";
import { moderation } from "./plugins/moderation.js";
const client = new SpearClient({ intents: Intents.default });
await client.use(moderation);
await client.start(process.env.DISCORD_TOKEN);
await client.deployCommands({ guildId: process.env.GUILD_ID });use accepts several plugins at once:
await client.use(moderation, welcome, tickets);Asynchronous setup
setup may be async — client.use awaits each one before moving to the next. Use
this to load data, connect to a database, or fetch remote config before
registering handlers.
export const tags = definePlugin({
name: "tags",
async setup(client) {
const store = await openTagStore(); // await anything you need first
client.register(
command({
name: "tag",
description: "Show a saved tag",
options: { name: option.string({ description: "Tag name", required: true }) },
run: (ctx) => ctx.reply(store.get(ctx.options.name) ?? "No such tag."),
}),
);
},
});Because use awaits setup, every plugin is fully installed before
client.start runs.
See also
- Client —
register,use,start, and the registries plugins write to. - File-based loading — discover commands, events and components from a directory instead of bundling them by hand.
Environment & dotenv
spearkit includes a tiny, dependency-free .env loader and a typed reader over process.env, so a bot needs no extra dotenv dependency. The client auto-loads .env on start(), and…
Graceful shutdown
A Ctrl-C or a container stop sends your process a signal. If you don't handle it, the process dies mid-flight — the gateway connection, scheduler timers, and any open database…