Documentação/Componentes
COMPONENTES / CALLBACKS

Callbacks de componentes

Trate botões e selects junto da definição visual, com IDs tipados e contexto de interação entregue pelo Core.

setCallbackcustomId tipadoAuto defer

Como o callback chega ao Core#

  1. 1
    O builder registra

    Ao combinar .setCustomId() e .setCallback(), o ComponentBuilder guarda a função somente em memória.

  2. 2
    O Discord envia o clique

    A interação chega ao InteractionCreate com o mesmo customId.

  3. 3
    O Core resolve

    O runtime encontra o callback, valida o contexto e entrega interaction, id e context.

  4. 4
    A resposta é concluída

    Se o callback não responder, o Core chama deferUpdate() para evitar o estado de falha no componente.

Callback de botão#

Defina o customId antes ou depois do callback; o registro acontece quando ambos estiverem presentes.

TSapprove-button.ts
const approveButton = new ButtonBuilder()  .setCustomId('project:approve')  .setLabel('Aprovar projeto')  .setStyle(ButtonStyle.Success)  .setCallback(async (interaction, id, context) => {    await approveProject({      action: String(id),      userId: interaction.user.id,      guildId: interaction.guildId    })    await interaction.update({      content: 'Projeto aprovado com sucesso.',      components: []    })    console.log('[Component] customId=' + context.customId)  })

O primeiro argumento é a interação real. O segundo é a parte lógica extraída do customId. O terceiro contém o ID completo, valores de select e o tipo do componente.

IDs segmentados e inferência#

O separador : transforma segmentos nomeados em um objeto tipado no callback.

TStyped-id.ts
new ButtonBuilder()  .setCustomId('project:42:archive')  .setLabel('Arquivar')  .setStyle(ButtonStyle.Secondary)  .setCallback(async (interaction, id) => {    // Para IDs segmentados, o editor infere as partes disponíveis.    console.log('[Project] callback=' + JSON.stringify(id))    await interaction.deferUpdate()  })

Callback de select#

Os valores selecionados estão em interaction.values e também no context normalizado.

TSenvironment-select.ts
const environmentSelect = new StringSelectMenuBuilder()  .setCustomId('project:environment')  .setPlaceholder('Escolha o ambiente')  .addOptions(    option => option.setLabel('Produção').setValue('production'),    option => option.setLabel('Desenvolvimento').setValue('development')  )  .setCallback(async (interaction, _id, context) => {    const environment = context.value    if (!environment) return    await projectRepository.setEnvironment(      interaction.guildId,      environment    )    await interaction.update({      content: 'Ambiente alterado para ' + environment,      components: []    })  })
TSseleção múltipla
new UserSelectMenuBuilder()  .setCustomId('team:members')  .setMinValues(1)  .setMaxValues(3)  .setCallback(async (interaction, _id, context) => {    const userIds = context.values    await teamRepository.replaceMembers(interaction.guildId, userIds)    await interaction.reply({      content: userIds.length + ' membros selecionados.',      flags: 64    })  })

Reply, update ou deferUpdate?#

MétodoResultado
interaction.update()Substitui a mensagem que contém o componente
interaction.reply()Cria uma resposta separada ao clique
interaction.deferUpdate()Confirma o clique sem mudar a mensagem
NenhumO Core faz deferUpdate() automaticamente
TSação demorada
.setCallback(async interaction => {  await interaction.deferReply({ flags: 64 })  const result = await runLongOperation()  await interaction.editReply(    result.ok ? 'Operação concluída.' : 'Não foi possível concluir.'  )})

Autorização continua sendo sua regra#

O runtime resolve o callback, mas a permissão de negócio precisa ser verificada no handler.

TScallback protegido
.setCallback(async interaction => {  const member = interaction.member  const canManage = member?.permissions?.has('ManageGuild')  if (!canManage) {    await interaction.reply({      content: 'Você não pode executar esta ação.',      flags: 64    })    return  }  await resetGuildConfiguration(interaction.guildId)  await interaction.update({ content: 'Configuração restaurada.', components: [] })})
  • Valide permissões no momento do clique, não apenas ao montar a mensagem.
  • Nunca confie em IDs ou valores recebidos sem validar existência e escopo.
  • Para fluxos privados, prefira respostas efêmeras iniciadas pelo próprio usuário.
  • Evite callbacks duplicados para o mesmo customId em módulos diferentes.