Guide
Implementing a Slash Command with Autocomplete in Discord.js v14
Step‑by‑step guide to add a slash command with autocomplete in Discord.js v14, including registration, interaction handling, error recovery, and rollback.
Published by Tasadduq Burney
25 Jul 2025, 01:19 UTC
3 min89.3K views0

Desired Outcome
Create a slash command that shows an autocomplete dropdown as the user types, using Discord.js v14. The bot should respond within the 3‑second window, handle registration safely, and provide a fallback if the interaction fails.
Prerequisites
- Node.js >= 16.9 installed.
- A Discord application with the
applications.commandsscope in its OAuth2 URL. - Bot token stored securely (e.g., environment variable
DISCORD_TOKEN). - A test guild where you have the
Manage Applicationspermission to register commands.
Procedure
- Install dependencies
Run in your project folder:
npm install discord.js@14 - Set up the client (file
index.js):const { Client, GatewayIntentBits, Routes } = require('discord.js'); const { REST } = require('@discordjs/rest'); const client = new Client({ intents: [GatewayIntentBits.Guilds] }); const token = process.env.DISCORD_TOKEN; const clientId = 'YOUR_APPLICATION_ID'; // replace with your app ID const guildId = 'YOUR_TEST_GUILD_ID'; // replace with a guild ID for testing // Command data for autocomplete const commandData = { name: 'search', description: 'Search for items with autocomplete', options: [ { type: 3, // STRING name: 'query', description: 'What to look for', autocomplete: true, required: true, }, ], }; // Register the command (run once or when updating) const rest = new REST({ version: '10' }).setToken(token); async function registerCommands() { try { await rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: [commandData], }); console.log('Registered autocomplete command.'); } catch (err) { if (err.status === 429) { console.warn('Rate limited while registering; retry after backoff.'); // implement exponential backoff here if needed } else { console.error('Failed to register command:', err); } } } client.once('ready', () => { console.log(`Logged in as ${client.user.tag}`); registerCommands(); }); // Interaction handler client.on('interactionCreate', async interaction => { if (!interaction.isChatInputCommand()) return; if (interaction.commandName === 'search') { // Autocomplete part if (interaction.isAutocomplete()) { const focusedValue = interaction.options.getFocused(); const choices = [ 'apple', 'banana', 'cherry', 'date', 'elderberry', 'fig', 'grape', 'honeydew', 'kiwi', 'lemon', ]; const filtered = choices.filter(choice => choice.toLowerCase().includes(focusedValue.toLowerCase()) ).slice(0, 25); // Discord allows max 25 choices try { await interaction.respond({ choices: filtered.map(name => ({ name, value: name })), }); } catch (err) { // InteractionFailed (e.g., timeout) – log and optionally follow up console.error('Autocomplete response failed:', err); // You cannot respond after the 3‑second window; just log. } return; } // Command execution after user selects a choice const query = interaction.options.getString('query', true); await interaction.reply(`You selected: ${query}`); } }); // Global error listener for unexpected issues client.on('error', console.error); client.login(token); - Run the bot
Execute:
node index.js
Expected Checks
- Console shows "Registered autocomplete command." after startup.
- In the test guild, typing
/searchdisplays a dropdown. - As you type in the
queryoption, the dropdown updates with matching items. - No "MissingAccess" or "InteractionFailed" errors appear in the console during normal use.
Recovery Options
- Rate‑limit during registration: If you receive HTTP 429, implement a simple backoff (e.g., wait 1‑2 seconds and retry) or defer registration to a later restart.
- Autocomplete timeout: The 3‑second limit is enforced by Discord. If you exceed it, the interaction fails and the user sees no suggestions. Keep the choice list small (<=25) and avoid synchronous heavy work; perform filtering quickly or defer to a worker thread if needed.
- Command deregistration (rollback): To remove the command, run:
This is useful when you need to revert a faulty registration.rest.delete(Routes.applicationGuildCommands(clientId, guildId), { body: [] }) .then(() => console.log('Command removed')) .catch(console.error);
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.