Lint
Catch djs-core anti-patterns with djs-core check.
djs-core check scans your src/**/*.ts files for patterns that break Discord APIs or fight the framework. It ships with @djs-core/dev — no extra install.
This is not ESLint or Biome. It only knows djs-core-specific rules (typed options, deprecated reply flags, missing command descriptions, etc.).
Quick start
Add a script
{"scripts": {"check": "djs-core check"}}Run the linter
Terminal window bun run check# ordjs-core checkAuto-fix what you can
Terminal window djs-core check --fix
CLI options
| Flag | Description |
|---|---|
--fix |
Auto-fix all fixable issues in place |
-p, --path <path> |
Project root (default: .) |
--rule <rule> |
Run a single rule by name |
# Lint another folderdjs-core check --path ./my-bot
# Only check ephemeral usagedjs-core check --rule no-ephemeral
# Fix deprecated constructorsdjs-core check --fix --rule no-generic-constructorExit codes
- 0 — no errors (warnings alone are OK)
- 1 — at least one error severity issue, or unknown
--rulename
Warnings never fail the command on their own. Handy for CI: block on errors, tolerate warnings while you migrate.
Rules
| Rule | Severity | Auto-fix | What it catches |
|---|---|---|---|
no-ephemeral |
error | yes | ephemeral: true in reply options |
no-generic-constructor |
warn | yes | new Button<T>() instead of .withData<T>() |
prefer-typed-options |
warn | no | interaction.options.getString() etc. in .run() |
require-description |
warn | yes | new Command() without .setDescription() |
no-ephemeral
Discord.js deprecated ephemeral: true. Use MessageFlags instead.
// badawait interaction.reply({ content: "secret", ephemeral: true });
// goodimport { MessageFlags } from "discord.js";
await interaction.reply({ content: "secret", flags: [MessageFlags.Ephemeral],});--fix rewrites the property and adds the MessageFlags import when missing.
no-generic-constructor
Component data types belong on .withData<T>(), not on the constructor.
Applies to: Button, Modal, StringSelectMenu, UserSelectMenu, RoleSelectMenu, ChannelSelectMenu, MentionableSelectMenu.
// badnew Button<{ id: string }>().setCustomId("x").run(...)
// goodnew Button().withData<{ id: string }>().setCustomId("x").run(...)prefer-typed-options
Declare options on the command builder and use the typed options parameter in .run().
// bad.run(async (interaction) => { const name = interaction.options.getString("name", true);})
// good.addStringOption((opt) => opt.setName("name").setRequired(true)).run(async (interaction, options) => { const name = options.name; // string})No auto-fix — you refactor the handler yourself.
require-description
Every slash command needs a description in Discord.
// badexport default new Command().run(async (interaction) => { ... });
// goodexport default new Command() .setDescription("Ping the bot") .run(async (interaction) => { ... });--fix inserts .setDescription("TODO: add a description") so you fill in the real text later.
CI
djs-core checkAdd it next to bun test in your pipeline. Use --fix locally, not in CI — let the job fail so you see what broke.
Run djs-core check --fix before opening a PR. Three rules auto-fix; prefer-typed-options is the one you still do by hand.