Guide débutant7 octobre 202618 min de lecture

Créer un bot Discord en 2026 : le guide complet discord.js

De zéro à un bot qui répond à des slash commands sur votre serveur, puis qui reste en ligne quand vous fermez le PC. Tout le code est fourni, et chaque erreur que vous risquez de rencontrer a sa ligne dans le tableau de dépannage.

Par Thomas Durand, Fastheberg

La plupart des guides « créer un bot Discord » s'arrêtent au moment où le bot répond « Pong » dans un terminal. C'est précisément là que les ennuis commencent : la commande n'apparaît pas, Discord affiche « l'application ne répond pas », le bot tombe quand le PC se met en veille. Ce guide va jusqu'au bout, parce que ce sont ces questions-là qu'on reçoit au support, pas celles sur la première ligne de code.

On utilise discord.js 14, la bibliothèque JavaScript la plus répandue, et les slash commands, qui sont devenues la façon normale d'interagir avec un bot. Comptez une heure si vous suivez tout, dont la moitié à lire. Si vous préférez Python, le même parcours existe avec discord.py.

1. Ce qu'est un bot Discord, techniquement

Un bot Discord est un programme ordinaire, qui tourne sur une machine ordinaire, et qui fait deux choses. Il garde une connexion WebSocket ouverte vers la « Gateway » de Discord, par laquelle Discord lui pousse des événements : quelqu'un a tapé une commande, un membre a rejoint, un message a été posté. Et il appelle l'API REST de Discord pour agir : répondre, envoyer un message, donner un rôle.

Schéma : le process du bot chez l'hébergeur, relié à Discord par une connexion WebSocket permanente et par l'API REST
Tant que la connexion WebSocket tient, le bot est « en ligne ». Si le programme s'arrête, la pastille passe au gris.

Deux conséquences pratiques à garder en tête pour la suite. D'abord, le bot doit tourner en continu : ce n'est pas une page web qu'on charge à la demande. Ensuite, c'est Discord qui décide quels événements il vous envoie, et vous le lui dites via les intents. On y revient à la section 4.

2. Ce qu'il vous faut

  • Node.js 18 ou plus récent. Prenez la LTS courante sur nodejs.org. Vérifiez avec node -v dans un terminal.
  • Un serveur Discord de test dont vous êtes administrateur. Créez-en un vide, ça évite de faire des essais devant votre communauté.
  • Un éditeur de code. VS Code fait très bien l'affaire, avec un terminal intégré.
  • Le Mode développeur activé dans Discord (Paramètres utilisateur, Avancés). Il ajoute « Copier l'identifiant » au clic droit sur un serveur ou un membre. Vous en aurez besoin pour l'identifiant du serveur de test.

Aucune connaissance avancée de JavaScript n'est nécessaire. Si vous savez ce qu'est une fonction et un objet, vous suivrez. Le code est commenté ligne à ligne là où ça compte.

3. Créer l'application dans le portail développeur

Tout commence sur discord.com/developers/applications. Côté Discord, un bot est une « application » à laquelle on attache un utilisateur bot. Cinq écrans, dans cet ordre.

Les cinq étapes du portail développeur : New Application, onglet Bot et Reset Token, intents privilégiés, OAuth2 URL Generator, invitation
  1. 1

    New Application. Donnez un nom, acceptez les conditions. Ce nom est celui du bot, vous pourrez le changer.

  2. 2

    Onglet Bot, bouton Reset Token. Discord affiche le token une seule fois. Copiez-le dans un endroit sûr, pas dans un message Discord, pas dans un fichier qui ira sur Git. Ce token est l'équivalent du mot de passe du bot. Dans le même onglet, décochez « Public Bot » si vous ne voulez pas que n'importe qui puisse l'inviter.

  3. 3

    Privileged Gateway Intents. Toujours dans l'onglet Bot, trois interrupteurs. Pour ce guide, laissez-les éteints jusqu'à la section 11, où on activera « Server Members Intent » pour le message de bienvenue. On explique pourquoi à la section suivante.

  4. 4

    OAuth2, URL Generator. Cochez les scopes bot et applications.commands. Le second est indispensable aux slash commands, et c'est l'oubli qui fait que « les commandes n'apparaissent pas ». Dans les permissions, cochez Send Messages, Embed Links et Read Message History. Pas Administrator : si le token fuit un jour, vous serez content d'avoir été radin.

  5. 5

    Ouvrir l'URL générée et choisir le serveur de test. Le bot apparaît dans la liste des membres, hors ligne. C'est normal : aucun programme ne tourne encore avec son token.

Notez au passage deux identifiants : l'Application ID (onglet General Information, c'est aussi l'identifiant du client) et l'identifiant de votre serveur de test (clic droit sur le serveur, Copier l'identifiant). Ils vont dans la configuration à la section 5.

4. Comprendre les intents avant d'écrire une ligne

Les intents sont la liste des familles d'événements que vous demandez à Discord de vous envoyer. Les déclarer dans le code suffit pour la plupart. Trois d'entre eux sont « privilégiés » : il faut en plus cocher la case correspondante dans le portail, et au-delà de 100 serveurs, obtenir l'accord de Discord.

Intents standard (Guilds, GuildMessages, GuildVoiceStates) à gauche, intents privilégiés (MessageContent, GuildMembers, GuildPresences) à droite

La bonne nouvelle : les slash commands n'ont besoin que de l'intent Guilds. Pas de Message Content, pas de case à cocher, et une empreinte mémoire minimale. C'est l'une des raisons pour lesquelles ce guide ne passe pas par les commandes à préfixe du type !ping.

Chaque intent de plus, c'est un flux d'événements en plus, que la bibliothèque met en cache. Sur un serveur actif, l'intent GuildMessages seul peut faire grossir la mémoire de plusieurs dizaines de mégaoctets. Ne demandez que ce que le bot utilise. Le jour où vous hébergez sur un plan à 128 Mo, cette règle fait la différence entre un bot stable et un bot qui redémarre.

5. Initialiser le projet Node.js

Dans un dossier vide, trois commandes :

Terminal
npm init -y
npm install discord.js dotenv
npm pkg set scripts.start="node index.js" scripts.deploy="node deploy-commands.js"

Créez ensuite un fichier .env à la racine avec vos trois valeurs, et un .gitignore qui l'exclut, avant même le premier commit :

.env
DISCORD_TOKEN=le_token_copié
CLIENT_ID=identifiant_application
GUILD_ID=identifiant_serveur_test
.gitignore
node_modules/
.env

Et voici l'arborescence qu'on va remplir. Un fichier par commande, un fichier pour l'enregistrement des commandes, un fichier pour le client. Ça paraît beaucoup pour trois commandes, mais c'est la structure qui tient quand le bot en a trente.

Arborescence
mon-bot/
├── .env
├── .gitignore
├── package.json
├── index.js              # le client : connexion et traitement des interactions
├── deploy-commands.js    # enregistre les commandes auprès de Discord
└── commands/
    ├── utility/
    │   ├── ping.js
    │   └── user.js
    └── fun/
        └── roll.js

6. Écrire les slash commands

Chaque commande est un module qui exporte deux choses : data, la description de la commande que Discord affichera (nom, description, options), et execute, la fonction appelée quand quelqu'un l'utilise. La plus simple possible :

commands/utility/ping.js
const { SlashCommandBuilder } = require('discord.js');

module.exports = {
  data: new SlashCommandBuilder()
    .setName('ping')
    .setDescription('Vérifie que le bot répond'),

  async execute(interaction) {
    // interaction.client.ws.ping : latence de la connexion WebSocket, en millisecondes
    await interaction.reply(`Pong ! Latence WebSocket : ${interaction.client.ws.ping} ms`);
  },
};

Une commande avec une option. Discord se charge de l'autocomplétion et de la validation : si l'utilisateur tape une lettre dans un champ numérique, votre code n'est jamais appelé.

commands/fun/roll.js
const { SlashCommandBuilder } = require('discord.js');

module.exports = {
  data: new SlashCommandBuilder()
    .setName('roll')
    .setDescription('Lance un dé')
    .addIntegerOption((option) =>
      option
        .setName('faces')
        .setDescription('Nombre de faces, 6 par défaut')
        .setMinValue(2)
        .setMaxValue(1000),
    ),

  async execute(interaction) {
    // Si l'option n'a pas été renseignée, getInteger renvoie null : on retombe sur 6
    const faces = interaction.options.getInteger('faces') ?? 6;
    const resultat = 1 + Math.floor(Math.random() * faces);
    await interaction.reply(`Dé à ${faces} faces : ${resultat}`);
  },
};

Et une commande qui va chercher une information côté Discord. Notez le await sur le fetch : c'est un appel à l'API REST, il prend quelques dizaines de millisecondes.

commands/utility/user.js
const { SlashCommandBuilder } = require('discord.js');

module.exports = {
  data: new SlashCommandBuilder()
    .setName('user')
    .setDescription('Affiche depuis quand un membre est sur le serveur')
    .addUserOption((option) =>
      option.setName('membre').setDescription('Le membre à inspecter, vous par défaut'),
    ),

  async execute(interaction) {
    const user = interaction.options.getUser('membre') ?? interaction.user;
    const membre = await interaction.guild.members.fetch(user.id);
    const depuis = membre.joinedAt.toLocaleDateString('fr-FR');
    await interaction.reply(`${user.username} est sur le serveur depuis le ${depuis}.`);
  },
};

7. Les enregistrer auprès de Discord

C'est l'étape que les débutants ratent, parce qu'elle n'existe pas avec les commandes à préfixe. Discord doit connaître vos commandes avant qu'elles s'affichent dans le menu. On les lui envoie une fois, via l'API REST, avec un script séparé. Il faudra le relancer à chaque fois que vous ajoutez une commande ou changez une description.

deploy-commands.js
require('dotenv').config();
const fs = require('node:fs');
const path = require('node:path');
const { REST, Routes } = require('discord.js');

// 1. On parcourt commands/*/*.js et on récupère la description (data) de chaque commande
const commands = [];
const foldersPath = path.join(__dirname, 'commands');
for (const folder of fs.readdirSync(foldersPath)) {
  const commandsPath = path.join(foldersPath, folder);
  for (const file of fs.readdirSync(commandsPath).filter((f) => f.endsWith('.js'))) {
    const command = require(path.join(commandsPath, file));
    if ('data' in command && 'execute' in command) {
      commands.push(command.data.toJSON());
    } else {
      console.warn(`[Attention] ${file} n'exporte pas data et execute, ignoré.`);
    }
  }
}

// 2. On les envoie à Discord, pour le serveur de test uniquement : disponible immédiatement
const rest = new REST().setToken(process.env.DISCORD_TOKEN);

(async () => {
  try {
    console.log(`Enregistrement de ${commands.length} commande(s)...`);
    const data = await rest.put(
      Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
      { body: commands },
    );
    console.log(`${data.length} commande(s) enregistrée(s) sur le serveur de test.`);
  } catch (error) {
    console.error(error);
  }
})();

Serveur de test ou global ? applicationGuildCommands enregistre les commandes pour un seul serveur, et elles apparaissent tout de suite. Parfait pour développer. Quand le bot est prêt pour tout le monde, remplacez par Routes.applicationCommands(process.env.CLIENT_ID) : les commandes deviennent globales, avec jusqu'à une heure de délai avant d'apparaître partout. Évitez de faire les deux en même temps, vous verriez chaque commande en double sur le serveur de test.

8. Le client : connexion et traitement des interactions

C'est le programme qui tourne en continu. Il charge les commandes dans une Collection (une Map améliorée), se connecte avec le token, et à chaque interaction reçue, retrouve la bonne commande et appelle son execute.

index.js
require('dotenv').config();
const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, Events, GatewayIntentBits, MessageFlags } = require('discord.js');

// Un seul intent : Guilds. Suffisant pour les slash commands.
const client = new Client({ intents: [GatewayIntentBits.Guilds] });

// Chargement des commandes, même parcours que deploy-commands.js
client.commands = new Collection();
const foldersPath = path.join(__dirname, 'commands');
for (const folder of fs.readdirSync(foldersPath)) {
  const commandsPath = path.join(foldersPath, folder);
  for (const file of fs.readdirSync(commandsPath).filter((f) => f.endsWith('.js'))) {
    const command = require(path.join(commandsPath, file));
    if ('data' in command && 'execute' in command) {
      client.commands.set(command.data.name, command);
    }
  }
}

client.once(Events.ClientReady, (c) => {
  console.log(`Connecté : ${c.user.tag}`);
});

client.on(Events.InteractionCreate, async (interaction) => {
  // Boutons, menus et modales arrivent aussi ici : on ne traite que les slash commands
  if (!interaction.isChatInputCommand()) return;

  const command = client.commands.get(interaction.commandName);
  if (!command) return;

  try {
    await command.execute(interaction);
  } catch (error) {
    console.error(error);
    // Message visible uniquement par la personne qui a lancé la commande
    const message = { content: 'Une erreur est survenue pendant cette commande.', flags: MessageFlags.Ephemeral };
    if (interaction.replied || interaction.deferred) {
      await interaction.followUp(message);
    } else {
      await interaction.reply(message);
    }
  }
});

// Une promesse rejetée sans catch ne doit pas tuer le process
process.on('unhandledRejection', (error) => {
  console.error('Promesse rejetée sans catch :', error);
});

client.login(process.env.DISCORD_TOKEN);

Le bloc try/catch autour de l'exécution est important. Sans lui, une commande qui plante laisse l'utilisateur devant « l'application ne répond pas » sans explication, et selon l'erreur, fait tomber tout le bot.

9. La règle des trois secondes

Quand quelqu'un lance une commande, Discord attend une réponse de votre bot pendant trois secondes. Passé ce délai, il affiche « L'application ne répond pas » et ignore ce que vous enverrez ensuite. Trois secondes, c'est long pour répondre « Pong », et très court pour interroger une API externe ou une base de données un peu lente.

Séquence : l'utilisateur tape /ping, Discord envoie une interaction au bot, le bot a trois secondes pour répondre ou différer, puis le message apparaît

La solution est de prévenir Discord tout de suite avec deferReply. L'utilisateur voit « Le bot réfléchit... », et vous avez quinze minutes pour compléter avec editReply :

Dans un execute() qui a du travail
async execute(interaction) {
  await interaction.deferReply();                 // répond « je réfléchis » en quelques ms

  const reponse = await fetch('https://api.exemple.fr/donnees'); // peut prendre 5 ou 10 s
  const donnees = await reponse.json();

  await interaction.editReply(`Résultat : ${donnees.valeur}`);   // jusqu'à 15 min plus tard
}

10. Lancer et tester

Deux commandes dans le terminal, dans cet ordre :

Terminal
npm run deploy
# Enregistrement de 3 commande(s)...
# 3 commande(s) enregistrée(s) sur le serveur de test.

npm start
# Connecté : MonBot#1234

Sur Discord, la pastille du bot passe au vert. Tapez / dans un salon : vos trois commandes apparaissent dans le menu avec leur description. Essayez /roll faces:20.

Si les commandes n'apparaissent pas alors que le script a dit « enregistrée(s) », fermez et rouvrez Discord : le client met parfois quelques secondes à rafraîchir la liste. Si ça ne vient toujours pas, le tableau de la section 14 vous attend.

11. Réagir aux événements : le message de bienvenue

Les commandes répondent à une demande. Les événements, eux, arrivent tout seuls : un membre rejoint, un message est supprimé, quelqu'un entre en vocal. L'exemple classique est le message de bienvenue, et il tombe bien, parce qu'il demande un intent privilégié. C'est l'occasion de voir ce que ça change.

D'abord, dans le portail développeur, onglet Bot, activez Server Members Intent. Ensuite, dans index.js, ajoutez l'intent et l'écouteur :

index.js, deux ajouts
// Remplace la ligne du client : on ajoute GuildMembers (privilégié, à cocher dans le portail)
const client = new Client({
  intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
});

// Après les autres écouteurs
client.on(Events.GuildMemberAdd, async (member) => {
  const salon = member.guild.systemChannel; // le salon « système » défini dans les paramètres du serveur
  if (!salon) return;
  await salon.send(`Bienvenue ${member} ! Pense à lire les règles avant de poster.`);
});

Redémarrez le bot. Si vous avez oublié la case dans le portail, il refusera de se connecter avec Used disallowed intents. C'est l'erreur la plus fréquente qu'on voit dans les consoles de nos clients, et de très loin.

12. Le garder en ligne 24h/24

Votre bot tourne tant que le terminal est ouvert. Fermez le portable, il passe hors ligne. Pour une communauté qui compte dessus, il faut une machine qui ne s'éteint pas. Trois options, détaillées dans le guide sur l'hébergement : un VPS que vous administrez, un hébergeur spécialisé avec un panel, ou votre PC si vous acceptez les coupures.

Sur un hébergeur comme le nôtre, ce qui change par rapport à votre machine tient en trois points :

  • Le .env est remplacé par les variables de l'onglet Startup du panel. Même noms, mêmes valeurs, mais aucun fichier secret à envoyer.
  • Vous n'envoyez pas node_modules. Le serveur lit le package.json et installe les dépendances au premier démarrage.
  • Le redémarrage après crash est géré par le panel. Votre process.on('unhandledRejection') reste utile pour éviter les crashs inutiles.

Un bot comme celui de ce guide tient largement dans le plan gratuit à 128 Mo. Le pas à pas chronométré est dans Déployer un bot discord.js en 5 minutes.

13. Sécurité et bonnes habitudes

  • Le token ne va jamais dans le code ni sur Git. Discord surveille les dépôts publics et révoque les tokens qu'il y trouve. Vous l'apprendrez par un bot qui tombe « sans raison ». Si ça arrive, Reset Token et on repart.
  • Permissions minimales à l'invitation. Un bot de sondages n'a pas besoin de bannir. Si le token fuit, les dégâts possibles restent proportionnés.
  • Vérifiez les permissions de celui qui lance la commande quand elle est sensible. setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) sur la définition de la commande la cache à ceux qui n'ont pas le droit.
  • Activez la double authentification sur le compte Discord qui possède l'application. Qui contrôle le compte contrôle le bot.
  • Mettez discord.js à jour de temps en temps avec npm update discord.js, et lisez les notes de version avant un changement de version majeure.

14. Dépannage

Les messages qu'on voit le plus souvent passer, en console ou dans Discord, et ce qu'ils signifient :

SymptômeCause probableCorrectif
Used disallowed intentsIntent privilégié demandé dans le code, pas activé dans le portail.Onglet Bot du portail, cocher l'intent. Ou le retirer du code s'il ne sert pas.
TokenInvalid / An invalid token was providedToken mal copié, régénéré, ou variable d'environnement vide.Vérifier le .env (pas d'espace, pas de guillemets), ou Reset Token.
Les commandes n'apparaissent pasdeploy-commands.js pas lancé, mauvais GUILD_ID, ou scope applications.commands absent à l'invitation.Relancer npm run deploy, vérifier l'identifiant, réinviter le bot avec les deux scopes.
« L'application ne répond pas »Plus de trois secondes avant reply(), ou une erreur avant la réponse.deferReply() en début d'execute, et regarder la console pour l'erreur.
DiscordAPIError[50013]: Missing PermissionsLe bot n'a pas le droit d'écrire dans ce salon, ou son rôle est trop bas.Permissions du salon et ordre des rôles dans les paramètres du serveur.
Cannot find module 'discord.js'Dépendances pas installées, ou pas dans le bon dossier.npm install à la racine du projet, là où est le package.json.
Chaque commande apparaît en doubleEnregistrée à la fois en global et sur le serveur de test.Choisir l'un ou l'autre ; vider les commandes de serveur en envoyant un tableau vide.

15. Questions fréquentes

Créer un bot Discord, c'est gratuit ?

Oui. Le portail développeur, la bibliothèque discord.js et Node.js sont gratuits. Le seul coût possible est l'hébergement si vous voulez que le bot reste en ligne quand votre PC est éteint, et il existe des plans gratuits pour ça.

Quelle version de Node.js faut-il ?

discord.js 14 demande Node.js 18 au minimum. Prenez la version LTS courante, 20 ou 22, c'est aussi ce que proposent les hébergeurs.

JavaScript ou Python pour un premier bot ?

Les deux marchent très bien. Si vous connaissez déjà l'un des deux langages, prenez celui-là. Sinon, discord.js a la plus grosse communauté francophone et le plus d'exemples en ligne ; discord.py est un peu plus court à écrire. Le guide Python est lié en bas de page.

Pourquoi mes slash commands n'apparaissent pas ?

Trois causes dans l'ordre : le script deploy-commands.js n'a pas été lancé, le GUILD_ID ne correspond pas au serveur où vous testez, ou le bot a été invité sans le scope applications.commands. Pour une commande globale, comptez jusqu'à une heure de propagation.

Puis-je laisser le bot tourner sur mon PC ?

Pour développer, oui. Pour une communauté qui compte dessus, non : le bot se déconnecte à chaque mise en veille, mise à jour ou coupure réseau. Un hébergeur ou un VPS règle le problème pour quelques euros, voire zéro.

Les commandes à préfixe comme !ping sont-elles encore possibles ?

Oui, mais elles demandent l'intent Message Content, qui est privilégié et que Discord n'accorde plus automatiquement au-delà de 100 serveurs. Les slash commands n'ont pas cette contrainte, c'est pour ça que ce guide les utilise.

Que faire si mon token a fuité ?

Allez dans le portail développeur, onglet Bot, Reset Token. L'ancien token cesse de fonctionner immédiatement. Mettez le nouveau dans votre .env ou dans les variables de votre hébergeur, puis redémarrez le bot.

Combien de RAM consomme un bot discord.js ?

Un bot qui n'utilise que l'intent Guilds et quelques commandes tourne confortablement sous les 128 Mo. La consommation grimpe avec les intents privilégiés, les caches de messages et tout traitement d'image ou d'audio.

Et maintenant

Vous avez un bot qui répond à trois commandes, accueille les nouveaux membres, et ne plante pas à la première promesse rejetée. La suite, c'est votre idée : boutons et menus déroulants (ils arrivent dans le même InteractionCreate), une base de données pour retenir des choses, ou un déploiement pour que tout ça tourne sans vous.

La documentation officielle de discord.js est excellente pour aller plus loin, et la structure de ce guide la suit volontairement : vous n'aurez rien à réapprendre.

À lire ensuite

Le bot est prêt. Reste à le laisser tourner.

Le plan gratuit donne 128 Mo de RAM, un demi vCore Intel Gold et 512 Mo de SSD : largement assez pour le bot de ce guide. Serveur livré en 120 secondes, sans carte bancaire.