spearkit

Introduction

discord.js++ — a developer-experience-first layer over discord.js. spearkit re-exports all of discord.js (so it's a drop-in replacement) and adds an ergonomic, fully type-safe API…

Contents

  1. Getting started — install, first bot, project layout.
  2. ClientSpearClient, intents, register, start, deployment.
  3. Commands — slash commands, subcommands, permissions, deployment.
  4. Options — typed option builders, choices, autocomplete.
  5. Components — buttons, selects, modals, custom-id routing.
  6. Events — the event() helper and the event registry.
  7. Contexts — reply helpers shared by every handler.
  8. Cooldowns — per-user/role/guild rate limiting.
  9. Scheduled tasks — cron and interval jobs.
  10. Prefix commands — classic !text commands.
  11. Logging — structured, leveled, scoped logging.
  12. Usage tracking — record who used what (store + Discord channel).
  13. Environment & dotenv — load .env and read typed env vars.
  14. Plugins — bundling features into reusable units.
  15. File-based loading — one file per command/event/component.
  16. Migrating from discord.js — the drop-in path.
  17. API reference — every exported symbol.

Why spearkit

  • Drop-in. import { Client, EmbedBuilder } from "spearkit" — every discord.js export is available, so you can migrate one file at a time.
  • Fully type-safe. No any or unknown leaks into your handlers. Option values, custom-id params and modal fields are all inferred from your definitions.
  • Co-located. A command's options and handler, a button's appearance and click logic, a modal's fields and submit logic — each lives in one place.
  • No boilerplate. No interactionCreate switch statements; spearkit routes commands, autocomplete, buttons, selects and modals for you.

Thirty-second tour

import { SpearClient, Intents, command, option, button, row, event } from "spearkit";

const client = new SpearClient({ intents: Intents.default });

const greet = command({
  name: "greet",
  description: "Greet someone",
  options: { who: option.user({ description: "Who", required: true }) },
  run: (ctx) => ctx.reply(`Hello ${ctx.options.who}!`), // who: User
});

const ping = button({
  id: "ping:{n}",
  label: "Ping",
  run: (ctx) => ctx.update(`pong #${ctx.params.n}`), // n: string
});

client.register(greet, ping, event("clientReady", (c) => console.log(c.user.tag)));
await client.start(process.env.DISCORD_TOKEN);
await client.deployCommands({ guildId: process.env.GUILD_ID });

On this page