Commands

Context Menu Commands

Context menu commands appear when a user right-clicks on a user or a message in Discord. Unlike slash commands, they don't have options or descriptions visible to the user — they simply appear as actions in the context menu.

Seyfert provides the ContextMenuCommand class specifically for these commands. In @Declare you must set the type explicitly — ApplicationCommandType.User or ApplicationCommandType.Message — and you omit the description, since context menu commands don't have one.

User Commands

User commands appear in the Apps section when right-clicking a user. They receive a MenuCommandContext with the target user:

import { ContextMenuCommand, Declare, type MenuCommandContext, type UserCommandInteraction} from 'seyfert';
import { ApplicationCommandType } from 'seyfert';


@Declare({
    name: 'User Info',
    type: ApplicationCommandType.User
})
export default class UserInfoCommand extends ContextMenuCommand {
    async run(ctx: MenuCommandContext<UserCommandInteraction>) {
        const target = ctx.target;

        await ctx.write({
            content: `**${target.username}** (${target.id})\nCreated: <t:${Math.floor(target.createdTimestamp / 1000)}:R>`,
        });
    }
}

The name property in @Declare for context menu commands can include spaces and uppercase letters, unlike slash commands.

Message Commands

Message commands appear in the Apps section when right-clicking a message. They receive the target message:

import { ContextMenuCommand, Declare, type MenuCommandContext, type MessageCommandInteraction } from 'seyfert';
import { ApplicationCommandType } from 'seyfert';

@Declare({
    name: 'Bookmark',
    type: ApplicationCommandType.Message
})
export default class BookmarkCommand extends ContextMenuCommand {
    async run(ctx: MenuCommandContext<MessageCommandInteraction>) {
        const message = ctx.target;

        await ctx.write({
            content: `Bookmarked message from **${message.author.username}**: ${message.content.slice(0, 100)}`,
        });
    }
}

Restricting Context

You can control where context menu commands can be used with contexts and integrationTypes:

import { ContextMenuCommand, Declare, type MenuCommandContext, type UserCommandInteraction } from 'seyfert';
import { ApplicationCommandType } from 'seyfert';

@Declare({
    name: 'Report User',
    type: ApplicationCommandType.User,
    // Only available in guilds
    contexts: ['Guild'],
    // Only for guild-installed apps
    integrationTypes: ['GuildInstall'],
    // Require specific permissions
    defaultMemberPermissions: ['ModerateMembers'],
})
export default class ReportUserCommand extends ContextMenuCommand {
    async run(ctx: MenuCommandContext<UserCommandInteraction>) {
        await ctx.write({ content: `Reported ${ctx.target.username}` });
    }
}

Type Guards

When handling commands that could be either chat or context menu commands, use type guards to differentiate:

This is useful in middlewares, since two commands rarely overlap during a run

import { Command, type AnyContext } from 'seyfert';

export default class MyCommand extends Command {
    async run(ctx: AnyContext) {
        if (ctx.isMenu()) {
            // ctx is MenuCommandContext
            ctx.client.logger.info(`Context menu used on: ${ctx.target}`);
        }

        if (ctx.isChat()) {
            // ctx is CommandContext (slash or prefix)
            ctx.client.logger.info(`Slash command: /${ctx.fullCommandName}`);
        }
    }
}