Autocomplete
Suggest values for a slash command's option while the member is still typing it.
You'll learn
- Turn on autocomplete for an option in its builder
- Answer with suggestions from an @Autocomplete handler
- Base one option's suggestions on another's value
Before this
Autocomplete fills Discord's option menu with suggestions as a member types: a product name, a city, a ticket number. Discord sends it as an interaction of its own, while the command hasn't been run yet, and it's answered with a list of choices rather than a reply.
When to use it
Use it when an option's valid values are too many for fixed choices, or change over time: the catalog, the member's own tickets, a search. Discord allows at most 25 fixed choices, and they're baked into the command when it's registered.
For a handful of values that never change, fixed choices with .addChoices(...) in the builder are simpler, and need
no handler. Autocomplete only suggests: the member can still send any value, so check it in the command's handler, or
with Validation.
Example
setAutocomplete(true) on the option is what makes Discord send the autocomplete interaction:
@CommandBuilder(CommandType.SLASH)
export class SearchCommandBuilder {
build(commandName: string) {
return new SlashCommandBuilder()
.setName(commandName)
.setDescription('Search the catalog')
.addStringOption(option =>
// setAutocomplete(true) is what makes Discord send the autocomplete interaction
option.setName('query').setDescription('What to look for').setRequired(true).setAutocomplete(true),
)
}
}@Controller()
export class SearchSlashController {
constructor(private readonly catalog: CatalogService) {}
@Command('search', SearchCommandBuilder)
async search(interaction: ChatInputCommandInteraction, { query }: { query: string }) {
await respond(interaction).send({ content: `Results for ${query}` })
}
@Autocomplete('search', 'query')
async completeQuery(interaction: AutocompleteInteraction) {
const { value } = interaction.options.getFocused(true)
// Discord shows at most 25 choices
const matches = this.catalog.find(value).slice(0, 25)
await interaction.respond(matches.map(name => ({ name, value: name })))
}
}@Autocomplete('search', 'query') binds completeQuery to the query option of /search. It reads what the member
has typed so far, and answers with up to 25 matches from the catalog. The command itself is handled by search, as
any slash command is.
How it works
- As the member types, Discord sends an
AutocompleteInteractionfor the option being typed, again for each change. - MeoCord finds the handler by the command's path and the focused option's name. A handler for one option wins over one for the whole command, so the two can live side by side.
- The handler runs with its guards and exception filters, but no interceptors, validation, cooldowns or
@Defer: it has three seconds and answers only once. A controller's@Cooldownskips it, and@Validate,@UsePipe,@Cooldownor@Deferon the handler itself stops the bot at startup. - It answers with discord.js's
interaction.respond(choices), notrespond(), since an autocomplete can't be replied to.
The handler's second argument holds the options the member has already filled in, so one option's suggestions can depend on another's value.
One handler for every option
Leave out the option name, @Autocomplete('search'), to handle every autocompleted option of the command, and branch
on interaction.options.getFocused(true).name yourself.
Autocomplete in a subcommand
The first argument is the command path, so subcommands work as they do for @Command:
@Autocomplete('settings notify email', 'address'). See Subcommands.
When no handler claims an option
MeoCord answers with an empty list, and logs which command and option have no handler, so the menu shows empty rather
than loading until it times out. An error no filter handles does the same. A filter that catches the handler's
own error answers it itself, with interaction.respond([]); after a guard's or a pipe's error, MeoCord still closes
the menu if the filter didn't.
Gotchas
- More than 25 choices are refused. Discord rejects the whole answer, and the menu shows nothing. Slice the list, as the example does.
- A slow lookup misses the three seconds. Autocomplete can't be deferred. Keep the lookup fast: cache what it searches, or search a smaller index.
- A handler for an option without autocomplete never runs. Discord never asks to complete it. The bot warns at
startup, and in the next major version (5.0) it refuses to start; turn it on in the builder with
setAutocomplete(true). - A suggestion isn't a guarantee. The member can ignore it and send anything. Check the value when the command runs.
- Only one handler completes an option. With two
@Autocompletehandlers for the same option of one command path, or for every option of one path, the one whose controller is listed first, or within one controller the one declared first, runs, and the other never does. The bot warns at startup, naming both, and in the next major version (5.0) it refuses to start. Keep one, or give the other an option or a path of its own.
Next steps
- Context menus: commands on a user or a message.
- Validation: checking the value the member finally sends.
- Mocks: testing an autocomplete handler, with
focusednaming the option typed in.