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, so you don't need to manually set the type in @Declare.

User Commands

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

import { , , type , type } from 'seyfert';
import {  } from 'seyfert/lib/types';


@({
    : 'User Info',
    : .
})
export default class  extends  {
    async (: <>) {
        const  = .;

        await .({
            : `**${.}** (${.})\nCreated: <t:${.(. / 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 { , , type , type  } from 'seyfert';
import {  } from 'seyfert/lib/types';

@({
    : 'Bookmark',
    : .
})
export default class  extends  {
    async (: <>) {
        const  = .;

        await .({
            : `Bookmarked message from **${..}**: ${..(0, 100)}`,
        });
    }
}

Restricting Context

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

import { , , type , type  } from 'seyfert';
import {  } from 'seyfert/lib/types';

@({
    : 'Report User',
    : .,
    // Only available in guilds
    : ['Guild'],
    // Only for guild-installed apps
    : ['GuildInstall'],
    // Require specific permissions
    : ['ModerateMembers'],
})
export default class  extends  {
    async (: <>) {
        await .({ : `Reported ${..}` });
    }
}

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 { , type  } from 'seyfert';

export default class  extends  {
    async (: ) {
        if (.()) {
            // ctx is MenuCommandContext
            ...(`Context menu used on: ${.}`);
        }

        if (.()) {
            // ctx is CommandContext (slash or prefix)
            ...(`Slash command: /${.}`);
        }
    }
}