aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Discord Bots

Creating Discord Slash Commands (discord.js v14)

Building a Discord slash command is the first step toward writing a modern Discord bot. The old message-based commands (like !ping) have been replaced by Discord's native slash command interface: when a user types /, commands and their options are listed automatically with validated input. This is now the official way to do it. In this tutorial we'll build a working slash command bot from scratch with discord.js v14 and Node.js 18+: project setup, defining commands with SlashCommandBuilder, deploying via the REST API, and responding through the interactionCreate event.

Project setup and dependencies

Node.js 18 or newer is required; discord.js v14 does not support older versions. Initialize the project in an empty folder and install the single dependency, the discord.js package:

mkdir discord-bot && cd discord-bot
npm init -y
npm install discord.js

We'll use CommonJS rather than ESM, so no extra configuration is needed in package.json; if you prefer modern import syntax you can add "type": "module". This guide uses require-based (CommonJS) JavaScript. Never hardcode secrets like your token and IDs; use a config.json file or environment variables:

{
  "token": "YOUR_BOT_TOKEN",
  "clientId": "APPLICATION_ID",
  "guildId": "TEST_SERVER_ID"
}
  • token: taken from the Discord Developer Portal → Bot tab.
  • clientId: your Application's ID.
  • guildId: the ID of your test server (enable Developer Mode, then right-click the server to copy it).

Defining commands with SlashCommandBuilder

Slash commands are defined with the SlashCommandBuilder class. This class is part of discord.js (formerly @discordjs/builders) and lets you set a command's name, description, and options in a type-safe way. Let's define a simple ping and a greet command that takes a parameter. Putting each command in its own file inside a commands folder is good practice, but for clarity we'll use a single array here:

const { SlashCommandBuilder } = require('discord.js');

const commands = [
  new SlashCommandBuilder()
    .setName('ping')
    .setDescription('Measures the bot latency'),

  new SlashCommandBuilder()
    .setName('greet')
    .setDescription('Greets a user')
    .addUserOption(option =>
      option
        .setName('user')
        .setDescription('The person to greet')
        .setRequired(true)
    ),
].map(command => command.toJSON());

module.exports = { commands };

You can add parameters with methods like .addUserOption, .addStringOption, and .addIntegerOption. setRequired(true) makes an option mandatory. The final toJSON() call converts the builder object into the raw JSON structure the Discord API expects.

Deploying commands with REST

Defining commands isn't enough; you have to register them with Discord. For that we write a separate deploy script using the REST client and the Routes helpers. During development use Routes.applicationGuildCommands: it registers to a single server instantly. Global commands (Routes.applicationCommands) appear on every server but can take up to an hour to propagate.

const { REST, Routes } = require('discord.js');
const { token, clientId, guildId } = require('./config.json');
const { commands } = require('./commands');

const rest = new REST({ version: '10' }).setToken(token);

(async () => {
  try {
    console.log(`Deploying ${commands.length} commands...`);

    const data = await rest.put(
      Routes.applicationGuildCommands(clientId, guildId),
      { body: commands },
    );

    console.log(`Successfully registered ${data.length} commands.`);
  } catch (error) {
    console.error(error);
  }
})();

Run this file once with node deploy-commands.js. You must run it again every time you change your commands. The rest.put method replaces (overwrites) the entire set of existing commands with the list you send, so any command you remove from the list gets deleted.

Client setup and the interactionCreate event

The bot itself lives in a separate file (index.js). When creating the Client object you must declare which events you want to receive using GatewayIntentBits. For slash commands only the Guilds intent is needed; you don't need to read message content. We respond to commands in the interactionCreate event and verify that the incoming interaction really is a command with interaction.isChatInputCommand():

const { Client, GatewayIntentBits } = require('discord.js');
const { token } = require('./config.json');

const client = new Client({
  intents: [GatewayIntentBits.Guilds],
});

client.once('clientReady', () => {
  console.log(`Logged in as ${client.user.tag}`);
});

client.on('interactionCreate', async (interaction) => {
  if (!interaction.isChatInputCommand()) return;

  if (interaction.commandName === 'ping') {
    await interaction.reply(`Pong! Latency: ${client.ws.ping}ms`);
  }

  if (interaction.commandName === 'greet') {
    const user = interaction.options.getUser('user');
    await interaction.reply(`Hello ${user}! Welcome.`);
  }
});

client.login(token);

Key points to note here:

  • isChatInputCommand(): only handles slash commands, not button or menu interactions. Without this check the code may throw when reading commandName.
  • interaction.options.getUser('user'): retrieves the value of the option you defined. Use getString, getInteger, and getBoolean for those types.
  • interaction.reply(): you must respond to an interaction within 3 seconds. For long-running work, call interaction.deferReply() first and then send the result with interaction.editReply().

To run the bot, deploy the commands first, then start the client:

node deploy-commands.js
node index.js

Common mistakes

  • "Missing Access" error: if you didn't invite the bot with the applications.commands scope, the commands won't register. In the OAuth2 URL, select both the bot and applications.commands scopes.
  • Command not showing: guild commands appear instantly, global commands propagate with a delay. Always use guild commands during development.
  • "Unknown interaction": the reply arrived later than 3 seconds. Use deferReply().

Frequently Asked Questions

What's the difference between a slash command and an old prefix command?

Prefix commands (!command) require reading message content and need Discord's privileged MessageContent intent. Slash commands are integrated into the Discord UI, provide autocomplete and input validation, and don't require the message content permission. For new bots, slash commands are the mandatory choice.

Why don't global commands appear immediately?

Discord caches global commands while distributing them to all servers, and that propagation can take up to an hour. For development and testing, register to a single server with Routes.applicationGuildCommands, because guild commands activate instantly.

Which Node.js version does discord.js v14 require?

discord.js v14 requires Node.js 18 or newer. On older versions you'll get an incompatibility error during installation. Check your version with node -v.

Want to take your bot to the next level? If you need help with subcommands, autocomplete, buttons, and a custom bot architecture, get in touch with me and let's bring your project to life together.

Devamı için