Ein Discord-Bot Button verwandelt deine Nachrichten von Textwänden in eine echte Oberfläche: Der Nutzer klickt, der Bot reagiert sofort. Mit discord.js lassen sich über Buttons, Dropdown-Menüs (String Select) und die ActionRow-Komponenten, die sie tragen, interaktive Abläufe bauen – etwa Rollen-Auswahl, Seitennavigation, Bestätigungsdialoge oder Support-Ticket-Panels. In dieser Anleitung baust du die Komponenten von Grund auf, hörst nach dem Senden korrekt auf Klicks und lernst, die häufigsten Fehler zu vermeiden.
Das Komponentenmodell: ActionRow, Button und Select
In Discord werden interaktive Elemente nie direkt an eine Nachricht gehängt – sie sitzen in Zeilen (Rows). Eine Nachricht kann höchstens 5 ActionRows enthalten. Jede Zeile trägt entweder bis zu fünf Buttons oder ein einzelnes Select-Menü; beides in derselben Zeile zu mischen ist nicht möglich. In discord.js v14 baust du diese Strukturen mit builder-Klassen:
ButtonBuilder— ein klickbarer Button mit Stil, Label undcustomId.StringSelectMenuBuilder— ein Dropdown mit festen Optionen.ActionRowBuilder— der Container, der Komponenten gruppiert.
Button-Stile stammen aus dem ButtonStyle-Enum: Primary (blau), Secondary (grau), Success (grün), Danger (rot) und Link. Link-Buttons öffnen eine URL und erhalten keine customId; die anderen erzeugen Interaktionen.
Einen Discord-Bot Button erstellen
Das folgende Beispiel fügt einem Slash-Command zwei Buttons hinzu. Das Feld customId ist das entscheidende Element: Es ist die eindeutige Kennung, mit der du Klick-Ereignisse unterscheidest, und sie darf bis zu 100 Zeichen lang sein.
const {
ButtonBuilder,
ButtonStyle,
ActionRowBuilder,
} = require('discord.js');
const confirm = new ButtonBuilder()
.setCustomId('order_confirm')
.setLabel('Bestätigen')
.setStyle(ButtonStyle.Success);
const cancel = new ButtonBuilder()
.setCustomId('order_cancel')
.setLabel('Abbrechen')
.setStyle(ButtonStyle.Danger);
const row = new ActionRowBuilder().addComponents(confirm, cancel);
await interaction.reply({
content: 'Möchtest du die Bestellung bestätigen?',
components: [row],
});
Die Nachricht zeigt nun zwei Buttons. Doch die Buttons sind noch "tot": Damit beim Klick etwas passiert, musst du einen separaten Listener einrichten.
Ein String-Select-Menü hinzufügen
Ein Dropdown ist aufgeräumter als Buttons, wenn du dem Nutzer mehrere Optionen anbieten willst. Jede Option hat ein sichtbares Label und einen value, der zu dir zurückkommt. Optional kannst du mit setMinValues und setMaxValues Mehrfachauswahl aktivieren.
const { StringSelectMenuBuilder } = require('discord.js');
const menu = new StringSelectMenuBuilder()
.setCustomId('role_select')
.setPlaceholder('Wähle eine Rolle')
.addOptions(
{ label: 'Entwickler', value: 'dev', emoji: '💻' },
{ label: 'Designer', value: 'design', emoji: '🎨' },
{ label: 'Community', value: 'community', emoji: '🤝' },
);
const menuRow = new ActionRowBuilder().addComponents(menu);
await interaction.reply({ components: [menuRow] });
Ein Select-Menü füllt eine ganze Zeile für sich allein; du kannst keinen Button in dieselbe Zeile setzen. Möchtest du mehrere Menüs oder Button-Gruppen, erstelle separate ActionRowBuilder-Objekte und füge sie alle dem components-Array hinzu.
Auf Interaktionen hören: interactionCreate und Collectors
Es gibt zwei Wege, Klicks abzufangen. Für dauerhafte, stets aktive Buttons nutzt du das Haupt-Event interactionCreate des Bots und verzweigst nach der customId:
client.on('interactionCreate', async (interaction) => {
if (interaction.isButton()) {
if (interaction.customId === 'order_confirm') {
await interaction.reply({ content: 'Bestellung bestätigt ✅', ephemeral: true });
}
}
if (interaction.isStringSelectMenu() && interaction.customId === 'role_select') {
const choice = interaction.values[0];
await interaction.reply({ content: `Deine Wahl: ${choice}`, ephemeral: true });
}
});
Für temporäre Abläufe, die an eine bestimmte Nachricht gebunden sind (Seitennavigation oder eine einmalige Bestätigung), ist createMessageComponentCollector praktischer. Ein Collector schließt nach einer festgelegten Zeit und hört nur auf die Komponenten dieser Nachricht:
const reply = await interaction.fetchReply();
const collector = reply.createMessageComponentCollector({
time: 60_000,
});
collector.on('collect', async (i) => {
if (i.user.id !== interaction.user.id) {
return i.reply({ content: 'Dieses Menü ist nicht für dich.', ephemeral: true });
}
await i.update({ content: `Auswahl: ${i.customId}`, components: [] });
});
collector.on('end', () => {
reply.edit({ components: [] }).catch(() => {});
});
Antworte immer auf jede Interaktion
Discord erwartet eine Antwort auf eine Interaktion innerhalb von 3 Sekunden; andernfalls sieht der Nutzer den Fehler "Diese Interaktion ist fehlgeschlagen". Kannst du nicht so schnell antworten (eine Datenbankabfrage, ein externer API-Aufruf usw.), rufe zuerst deferUpdate() oder deferReply() auf und fahre mit editReply() fort. Unterscheide die Antwortmethoden so:
reply()— sendet eine ganz neue Antwort.update()— bearbeitet die ursprüngliche Nachricht, auf der der Button war (ideal zum Schließen von Menüs).deferUpdate()/deferReply()— ein "Denkt nach"-Status, der Zeit für lange Operationen gewinnt.
Die Option ephemeral: true zeigt die Antwort nur der Person, die geklickt hat, und hält den Kanal für Rollen-Panels und Fehlermeldungen sauber.
Häufige Fragen
Warum ist die customId so wichtig?
Weil die customId die einzige Möglichkeit ist zu erkennen, welcher Button oder welches Menü geklickt wurde. Halte sie eindeutig und lesbar; bei Bedarf kannst du Daten (eine Nutzer- oder Nachrichten-ID) einbetten, etwa ticket_close_12345, und sie beim Klick auswerten.
Wie viele Buttons kann eine Nachricht haben?
Bis zu 5 ActionRows pro Nachricht und bis zu 5 Buttons pro Button-Zeile, also insgesamt bis zu 25 Buttons. Ein Select-Menü hingegen füllt eine Zeile für sich allein.
Funktionieren alte Buttons nach einem Neustart des Bots nicht mehr?
Collectors sind an den laufenden Bot gebunden und gehen daher beim Neustart verloren. Möchtest du dauerhafte Buttons, nutze einen globalen interactionCreate-Listener mit customId-basiertem Routing statt eines Collectors; so werden Klicks bei jedem Start des Bots weiterhin verarbeitet.
Brauchst du einen interaktiven Discord-Bot? Ich kann ein sauberes, wartbares Setup für Rollen-Panels, Ticket-Systeme oder eigene Select-Menü-Abläufe aufbauen. Kontaktiere mich und lass uns über dein Projekt sprechen.