Als je een moderne Discord-bot bouwt, is leren hoe je een discordpy slash komut — een slash-commando in discord.py — schrijft niet langer optioneel. De oude !commando-stijl van op prefix gebaseerde tekstcommando's heeft plaatsgemaakt voor slash-commando's, waarbij Discord autocompletie en parameterhints toont terwijl de gebruiker typt. De module app_commands die met discord.py 2.x meekomt, is de officiële manier, en goed opgezet geeft die je een schone, onderhoudbare codebasis.
In deze gids beginnen we bij nul en werken we toe naar commando's met parameters, een modulaire structuur met Cogs, en nette foutafhandeling. Alle voorbeelden werken met discord.py 2.x en Python 3.9+.
Omgeving en een basis-botskelet
Installeer eerst de bibliotheek in een actuele versie. Oudere releases bevatten app_commands niet, dus 2.x is vereist:
pip install -U "discord.py>=2.3"
Nu een minimale bot. De klasse commands.Bot stelt automatisch een CommandTree beschikbaar — die je slash-commando's bevat — via bot.tree:
import discord
from discord import app_commands
from discord.ext import commands
intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)
@bot.event
async def on_ready():
print(f"Ingelogd als {bot.user}")
bot.run("TOKEN")
Je hebt de message_content-intent niet nodig voor slash-commando's; die is alleen relevant voor prefix-commando's. Zet je token nooit hardcoded in de code — lees hem uit een omgevingsvariabele.
Je eerste slash-commando en synchroniseren
Het eenvoudigste slash-commando definieer je met de decorator bot.tree.command. De eerste parameter is altijd getypeerd als discord.Interaction:
@bot.tree.command(name="hallo", description="Zegt hallo")
async def hallo(interaction: discord.Interaction):
await interaction.response.send_message(
f"Hallo, {interaction.user.mention}!"
)
Het cruciale punt: het commando definiëren is niet genoeg — je moet het naar Discord synchroniseren. Een globale sync (await bot.tree.sync()) geldt voor elke server maar kan tot een uur duren om door te komen. Dat is veel te traag tijdens het ontwikkelen; synchroniseer in plaats daarvan naar één testserver — dat is direct:
GUILD = discord.Object(id=123456789012345678) # ID van de testserver
@bot.event
async def on_ready():
bot.tree.copy_global_to(guild=GUILD)
await bot.tree.sync(guild=GUILD)
print("Commando's gesynchroniseerd naar server")
Let op: sync() is een API-aanroep met rate limits. Roep sync() niet blindelings aan bij elke start; voer het alleen uit wanneer commando's echt veranderen. Een veelgebruikt patroon is om synchronisatie te koppelen aan een verborgen prefix-commando dat alleen de eigenaar kan activeren.
Parameters, beschrijvingen en keuzes
De kracht van slash-commando's komt van parameters met type-hints. discord.py leest je Python-type-hints en genereert het juiste invoerveld: int wordt een getal, discord.Member een gebruikerskiezer, bool een schakelaar.
@bot.tree.command(name="optellen", description="Telt twee getallen op")
@app_commands.describe(a="Eerste getal", b="Tweede getal")
async def optellen(interaction: discord.Interaction, a: int, b: int):
await interaction.response.send_message(f"{a} + {b} = {a + b}")
@app_commands.describe stelt de hulptekst in die naast elke parameter verschijnt — belangrijk voor de bruikbaarheid. Om een vaste set opties aan te bieden, gebruik je Choice:
@bot.tree.command(name="moeilijkheid", description="Kies een moeilijkheid")
@app_commands.describe(niveau="Spelmoeilijkheid")
@app_commands.choices(niveau=[
app_commands.Choice(name="Makkelijk", value="easy"),
app_commands.Choice(name="Moeilijk", value="hard"),
])
async def moeilijkheid(interaction: discord.Interaction,
niveau: app_commands.Choice[str]):
await interaction.response.send_message(
f"Gekozen: {niveau.name} ({niveau.value})"
)
Als het berekenen van het antwoord lang duurt (bijvoorbeeld een API-aanroep), roep dan eerst await interaction.response.defer() aan om binnen het responsvenster van 3 seconden te blijven, en stuur het resultaat daarna met await interaction.followup.send(...).
Modulaire structuur met Cogs
Tientallen commando's in één bestand beheren valt snel uit elkaar. Met commands.Cog groepeer je verwante commando's in een klasse en splits je ze over aparte bestanden (extensions). Binnen een Cog gebruik je de decorator @app_commands.command, en de eerste parameter wordt self:
# cogs/tools.py
import discord
from discord import app_commands
from discord.ext import commands
class Tools(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@app_commands.command(name="ping", description="Toont de latency")
async def ping(self, interaction: discord.Interaction):
latency = round(self.bot.latency * 1000)
await interaction.response.send_message(f"Pong! {latency}ms")
async def setup(bot: commands.Bot):
await bot.add_cog(Tools(bot))
De juiste plek om extensions te laden is setup_hook, die draait voordat de bot verbindt. De bot subclassen is de schoonste aanpak:
class Bot(commands.Bot):
async def setup_hook(self):
await self.load_extension("cogs.tools")
await self.tree.sync() # synchroniseer naar een server tijdens dev
intents = discord.Intents.default()
bot = Bot(command_prefix="!", intents=intents)
bot.run("TOKEN")
Om meerdere commando's onder één naam te bundelen, gebruik je app_commands.Group; dat levert subcommando's op zoals /instellingen taal en /instellingen melding.
Foutafhandeling
Als er binnen een commando een uitzondering optreedt, ziet de gebruiker alleen een stille fout. Het is het beste om één centrale foutafhandelaar voor alle slash-commando's te definiëren. CommandTree biedt daar precies een error-decorator voor:
@bot.tree.error
async def on_app_command_error(
interaction: discord.Interaction,
error: app_commands.AppCommandError,
):
if isinstance(error, app_commands.MissingPermissions):
bericht = "Je hebt geen rechten voor dit commando."
else:
bericht = "Er ging iets mis, probeer het opnieuw."
# response als nog niet verstuurd, anders followup
if interaction.response.is_done():
await interaction.followup.send(bericht, ephemeral=True)
else:
await interaction.response.send_message(bericht, ephemeral=True)
ephemeral=True toont het bericht alleen aan de persoon die het commando uitvoerde — ideaal voor foutmeldingen. Je kunt een rechtencontrole toevoegen met @app_commands.checks.has_permissions(...) op het commando, en de fout opvangen in de bovenstaande afhandelaar.
Veelgestelde vragen
Waarom verschijnen mijn slash-commando's niet in Discord?
Het is bijna altijd een synchronisatieprobleem. Een globale sync() heeft tijd nodig om door te komen; synchroniseer tijdens het ontwikkelen rechtstreeks naar je testserver (guild=...) zodat ze direct verschijnen. Zorg er ook voor dat de bot is uitgenodigd met de scope applications.commands.
Wat is het verschil tussen app_commands en het oude commands.command?
commands.command zijn op prefix gebaseerde tekstcommando's (!commando). app_commands zijn Discords native slash-commando's: ze bieden autocompletie, typevalidatie en visuele parametervelden. Voor nieuwe bots hebben slash-commando's de voorkeur.
Moet ik bij elke start sync aanroepen?
Nee. sync() heeft rate limits, en onnodige aanroepen kunnen je beperkt opleveren. Synchroniseer alleen wanneer commandodefinities veranderen; in productie koppelt men dit vaak aan een handmatig commando dat alleen de eigenaar kan gebruiken.
Wil je je bot naar een hoger niveau tillen? Van slash-commando's tot moderatiesystemen, muziekbackends en aangepaste dashboard-integraties: ik kan helpen bij je discord.py-projecten. Deel je idee en laten we het samen bouwen: neem contact op.