Een Discord slash-commando bouwen is de eerste stap naar het schrijven van een moderne Discord-bot. De oude op berichten gebaseerde commando's (zoals !ping) zijn vervangen door Discords ingebouwde slash command-interface: wanneer een gebruiker / typt, worden commando's en hun opties automatisch weergegeven met gevalideerde invoer. Dit is nu de officiële manier. In deze tutorial bouwen we vanaf nul een werkende slash-commando-bot met discord.js v14 en Node.js 18+: projectopzet, commando's definiëren met SlashCommandBuilder, deployen via de REST-API en reageren via het interactionCreate-event.
Projectopzet en afhankelijkheden
Node.js 18 of nieuwer is vereist; discord.js v14 ondersteunt oudere versies niet. Initialiseer het project in een lege map en installeer de enige afhankelijkheid, het discord.js-pakket:
mkdir discord-bot && cd discord-bot
npm init -y
npm install discord.js
We gebruiken CommonJS in plaats van ESM, dus er is geen extra configuratie nodig in package.json; als je de moderne import-syntaxis verkiest, kun je "type": "module" toevoegen. Deze gids gebruikt op require gebaseerde (CommonJS) JavaScript. Codeer geheimen zoals je token en ID's nooit hard in de code; gebruik een config.json-bestand of omgevingsvariabelen:
{
"token": "JOUW_BOT_TOKEN",
"clientId": "APPLICATIE_ID",
"guildId": "TEST_SERVER_ID"
}
- token: opgehaald uit de Discord Developer Portal → tabblad Bot.
- clientId: het ID van je Application.
- guildId: het ID van je testserver (zet ontwikkelaarsmodus aan en klik met de rechtermuisknop op de server om het te kopiëren).
Commando's definiëren met SlashCommandBuilder
Slash-commando's worden gedefinieerd met de klasse SlashCommandBuilder. Deze klasse maakt deel uit van discord.js (voorheen @discordjs/builders) en laat je de naam, beschrijving en opties van een commando op een typeveilige manier instellen. Laten we een eenvoudig ping- en een groet-commando definiëren dat een parameter aanneemt. Elk commando in een eigen bestand binnen een commands-map plaatsen is een goede praktijk, maar voor de duidelijkheid gebruiken we hier één array:
const { SlashCommandBuilder } = require('discord.js');
const commands = [
new SlashCommandBuilder()
.setName('ping')
.setDescription('Meet de latentie van de bot'),
new SlashCommandBuilder()
.setName('groet')
.setDescription('Begroet een gebruiker')
.addUserOption(option =>
option
.setName('gebruiker')
.setDescription('De persoon om te begroeten')
.setRequired(true)
),
].map(command => command.toJSON());
module.exports = { commands };
Je kunt parameters toevoegen met methoden zoals .addUserOption, .addStringOption en .addIntegerOption. setRequired(true) maakt een optie verplicht. De afsluitende toJSON()-aanroep zet het builder-object om naar de ruwe JSON-structuur die de Discord-API verwacht.
Commando's deployen met REST
Commando's definiëren is niet genoeg; je moet ze registreren bij Discord. Daarvoor schrijven we een apart deploy-script dat de REST-client en de Routes-helpers gebruikt. Gebruik tijdens de ontwikkeling Routes.applicationGuildCommands: dit registreert direct op één server. Globale commando's (Routes.applicationCommands) verschijnen op elke server, maar de verspreiding kan tot een uur duren.
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(`${commands.length} commando's deployen...`);
const data = await rest.put(
Routes.applicationGuildCommands(clientId, guildId),
{ body: commands },
);
console.log(`${data.length} commando's succesvol geregistreerd.`);
} catch (error) {
console.error(error);
}
})();
Voer dit bestand één keer uit met node deploy-commands.js. Je moet het opnieuw uitvoeren telkens wanneer je je commando's wijzigt. De methode rest.put vervangt (overschrijft) de volledige set bestaande commando's met de lijst die je verstuurt, dus elk commando dat je uit de lijst verwijdert, wordt verwijderd.
Client-opzet en het interactionCreate-event
De bot zelf woont in een apart bestand (index.js). Bij het aanmaken van het Client-object moet je met GatewayIntentBits aangeven welke events je wilt ontvangen. Voor slash-commando's is alleen de Guilds-intent nodig; je hoeft de berichtinhoud niet te lezen. We reageren op commando's in het interactionCreate-event en verifiëren dat de binnenkomende interactie echt een commando is met 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(`Ingelogd als ${client.user.tag}`);
});
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
await interaction.reply(`Pong! Latentie: ${client.ws.ping}ms`);
}
if (interaction.commandName === 'groet') {
const user = interaction.options.getUser('gebruiker');
await interaction.reply(`Hallo ${user}! Welkom.`);
}
});
client.login(token);
Belangrijke aandachtspunten hier:
- isChatInputCommand(): verwerkt alleen slash-commando's, geen knop- of menu-interacties. Zonder deze controle kan de code een fout geven bij het lezen van
commandName. - interaction.options.getUser('gebruiker'): haalt de waarde op van de optie die je hebt gedefinieerd. Gebruik
getString,getIntegerengetBooleanvoor die typen. - interaction.reply(): je moet binnen 3 seconden op een interactie reageren. Roep voor langlopend werk eerst
interaction.deferReply()aan en stuur daarna het resultaat metinteraction.editReply().
Om de bot te starten, deploy je eerst de commando's en start je daarna de client:
node deploy-commands.js
node index.js
Veelvoorkomende fouten
- "Missing Access"-fout: als je de bot niet hebt uitgenodigd met de
applications.commands-scope, worden de commando's niet geregistreerd. Selecteer in de OAuth2-URL zowel debot- als deapplications.commands-scope. - Commando verschijnt niet: guild-commando's verschijnen direct, globale commando's verspreiden zich met vertraging. Gebruik tijdens de ontwikkeling altijd guild-commando's.
- "Unknown interaction": het antwoord kwam later dan 3 seconden. Gebruik
deferReply().
Veelgestelde vragen
Wat is het verschil tussen een slash-commando en een oud prefix-commando?
Prefix-commando's (!commando) vereisen het lezen van de berichtinhoud en hebben Discords geprivilegieerde MessageContent-intent nodig. Slash-commando's zijn geïntegreerd in de Discord-interface, bieden autoaanvulling en invoervalidatie en vereisen geen toestemming voor berichtinhoud. Voor nieuwe bots zijn slash-commando's de verplichte keuze.
Waarom verschijnen globale commando's niet meteen?
Discord cachet globale commando's terwijl ze naar alle servers worden gedistribueerd, en die verspreiding kan tot een uur duren. Registreer voor ontwikkeling en testen op één server met Routes.applicationGuildCommands, omdat guild-commando's direct actief worden.
Welke Node.js-versie vereist discord.js v14?
discord.js v14 vereist Node.js 18 of nieuwer. Op oudere versies krijg je tijdens de installatie een incompatibiliteitsfout. Controleer je versie met node -v.
Wil je je bot naar een hoger niveau tillen? Als je hulp nodig hebt met subcommando's, autoaanvulling, knoppen en een aangepaste botarchitectuur, neem dan contact met me op en laten we jouw project samen tot leven brengen.