Only show guides for your app
The commands that live in the right-click menu, not the slash list — "User Info" on a member, "Report" on a message. Build both kinds in discord.js and read the target the user clicked.
Not every command belongs in the / list. Some make more sense as a right-click: right-click a member for User Info, right-click a message for Report. These are context menu commands — they take no typed options, just the thing the user clicked on. This recipe builds one of each and reads the target.
| 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 |
Context menus register the same way slash commands do — through application.commands.set([...]) in ClientReady — and arrive through the same InteractionCreate event. Only the builder and the way you read input change.
| Kind | Where it appears | What your code gets |
|---|---|---|
| User | right-click a member → Apps | the user that was clicked (interaction.targetUser) |
| Message | right-click a message → Apps | the message that was clicked (interaction.targetMessage) |
🎯 Good to know: A context menu command takes no options — the "input" is whatever the user right-clicked. That's the whole point: it acts on something already on screen, so there's nothing to type.
Import the builder and the type enum:
const {
Client, Events, GatewayIntentBits,
ContextMenuCommandBuilder, ApplicationCommandType,
} = require('discord.js');
A context menu is a name plus a type. Build one of each:
const userInfo = new ContextMenuCommandBuilder()
.setName('User Info')
.setType(ApplicationCommandType.User);
const reportMsg = new ContextMenuCommandBuilder()
.setName('Report Message')
.setType(ApplicationCommandType.Message);
Register them in ClientReady, in the same array as your slash commands — they all live in one command list:
client.once(Events.ClientReady, async (c) => {
await c.application.commands.set([userInfo, reportMsg]);
console.log(`Listening as ${c.user.tag}`);
});
💡 Tip: The name is what users see in the right-click menu, so write it in plain Title Case —
User Info, notuser_info. Unlike slash command names, spaces and capitals are allowed.
Both arrive through InteractionCreate. Guard each with its own check, then read the target:
client.on(Events.InteractionCreate, async (interaction) => {
if (interaction.isUserContextMenuCommand() && interaction.commandName === 'User Info') {
const user = interaction.targetUser;
await interaction.reply({
content: `**${user.tag}**\nID: ${user.id}\nJoined Discord: <t:${Math.floor(user.createdTimestamp / 1000)}:R>`,
ephemeral: true,
});
return;
}
if (interaction.isMessageContextMenuCommand() && interaction.commandName === 'Report Message') {
const message = interaction.targetMessage;
await interaction.reply({
content: `Reported a message from ${message.author.tag}.`,
ephemeral: true,
});
}
});
The two things that make this work:
isUserContextMenuCommand() / isMessageContextMenuCommand() tell the two kinds apart (and apart from slash commands and buttons).interaction.targetUser is the clicked member; interaction.targetMessage is the clicked message — with its .author, .content, .id, everything a message has.The <t:…:R> in the reply is a Discord timestamp: give it a Unix time in seconds and Discord renders a live "3 years ago" that updates itself. Handy for join dates and "created" fields.
⚠️ Heads up: Reading a message's
.contentinside a Message context menu needs the Message Content privileged intent enabled on the Bot page — even though the command itself doesn't. Reporting that a message exists (author, ID) works without it; showing what it said does not.
| Use a... | when the action... |
|---|---|
| Context menu | targets one user or one message already on screen — info, report, quote, warn |
| Slash command | needs typed input, or isn't about a specific thing you can point at |
Many bots offer both: /userinfo @someone for typing a name, plus a User Info right-click for the person in front of you. They can share the same reply-building code.
Start the bot, then in Discord right-click a member and open Apps → User Info, and right-click any message and open Apps → Report Message. Each replies privately (ephemeral). First-time registration can take a few minutes to appear, same as slash commands.
applications.commands scope.commandName exactly and reply promptly.targetMessage.content is empty — the Message Content intent isn't enabled. Turn it on in the Developer Portal, or don't rely on the text.return after handling one. Return after each branch so a single interaction takes one path.