Autocomplete for command options

The live suggestions that pop up as someone types a slash option — perfect for long or changing lists a fixed choice list can't hold. Build it in discord.js and filter results as they type.

A slash option can offer a fixed list with .addChoices() — but that caps at 25 and can't change. When the list is long (every item in your shop, every open ticket) or built at runtime, you want autocomplete: suggestions that appear and filter as the user types. This recipe wires up an autocomplete option and answers it.

At a glance
You need a working bot from the discord.js guide, and comfort with Slash commands in depth
Plan free or premium — free runs while your session timer lasts
Time about fifteen minutes

Autocomplete adds a second round trip: as the user types, Discord asks your bot for suggestions; when they pick or finish, the command runs normally. So one command means handling two interaction kinds.

Mark the option

Add .setAutocomplete(true) to a string, integer, or number option. That's the only change to the builder — no .addChoices(), because you'll supply them live:

const {
  Client, Events, GatewayIntentBits, SlashCommandBuilder,
} = require('discord.js');

const FRUITS = ['Apple', 'Apricot', 'Banana', 'Blueberry', 'Cherry',
                'Grape', 'Mango', 'Orange', 'Peach', 'Pear'];

const fruit = new SlashCommandBuilder()
  .setName('fruit')
  .setDescription('Pick a fruit')
  .addStringOption(option =>
    option.setName('name')
      .setDescription('Start typing a fruit')
      .setAutocomplete(true)
      .setRequired(true));

Answer the suggestions

As the user types, an autocomplete interaction arrives. Guard it with isAutocomplete(), read what they've typed so far with getFocused(), filter your list, and send back up to 25 suggestions with interaction.respond():

client.on(Events.InteractionCreate, async (interaction) => {
  if (interaction.isAutocomplete()) {
    const focused = interaction.options.getFocused().toLowerCase();
    const matches = FRUITS
      .filter(f => f.toLowerCase().startsWith(focused))
      .slice(0, 25);
    await interaction.respond(matches.map(f => ({ name: f, value: f })));
    return;
  }

  if (interaction.isChatInputCommand() && interaction.commandName === 'fruit') {
    await interaction.reply(`You picked ${interaction.options.getString('name')}.`);
  }
});

The moving parts:

  • getFocused() returns the text typed so far in the option currently being filled — an empty string when they've typed nothing yet. (Use getFocused(true) to get { name, value } when a command has more than one autocomplete option and you need to know which one.)
  • respond([...]) sends the suggestions. Each is a { name, value } pair: name is what the user sees in the dropdown, value is what your command receives when they pick it — they can differ (show "Apple 🍎", send "apple").
  • .slice(0, 25) matters: Discord accepts at most 25 suggestions and rejects the response if you send more.

Then the normal command handler reads the chosen value with getString('name') exactly as any option.

⚠️ Heads up: An autocomplete response has the same ~3-second deadline as any interaction, and you cannot deferReply() it. Keep the filter fast — search an in-memory array or a quick indexed query, not a slow API call on every keystroke.

Suggestions from real data

The point of autocomplete is a list you couldn't hardcode. Swap the static array for anything: rows from your SQLite database, open tickets, a user's own items. The shape stays the same — filter by focused, cap at 25, map to { name, value }:

// Suggesting the caller's own to-do items from a database
const focused = interaction.options.getFocused().toLowerCase();
const items = getUserItems(interaction.user.id)           // your query
  .filter(row => row.title.toLowerCase().includes(focused))
  .slice(0, 25);
await interaction.respond(items.map(row => ({ name: row.title, value: String(row.id) })));

Note the value is the row's ID while the name is the human title — so your command handler gets a clean ID to look up, not the display text.

Autocomplete, choices, or a select menu?

Use... when...
.addChoices() a short fixed list (≤25) that never changes — difficulty, region
Autocomplete a long or runtime list the user filters by typing
Select menu the user picks after the command runs, from options you present in the reply

Verify it works

Register /fruit, start the bot, and type /fruit name: followed by a letter. Suggestions filter as you type — ap narrows to Apple and Apricot. Pick one and the command replies with it. If the dropdown stays empty, your respond() isn't running or is sending nothing that matches.

Troubleshooting

  • No suggestions appear — the isAutocomplete() branch is missing, throws, or returns more than 25 items. Check the console for an error and confirm the .slice(0, 25).
  • Interaction has already been acknowledged — you called respond() twice, or tried deferReply(). Respond exactly once per autocomplete interaction, and never defer it.
  • The option won't autocomplete at all.setAutocomplete(true) and .addChoices() are mutually exclusive; a builder can't have both on the same option.
  • Wrong value reaches the command — you set value to the display text instead of an ID. The command reads value, so put the machine-friendly string there.

Next steps

Was this guide helpful?