A powerful Node.js library for interacting with the Discord API, enabling developers to create Discord bots and applications with full API coverage
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive message components system including action rows, buttons, select menus, modals, and text inputs with builder patterns for creating interactive Discord interfaces.
Container component that holds other interactive components in a horizontal row.
/**
* Builder for creating action rows
* @template ComponentType The type of components this action row can contain
*/
class ActionRowBuilder<ComponentType extends AnyComponentBuilder = AnyComponentBuilder> {
constructor(data?: Partial<ActionRowData<ActionRowComponentData | JSONEncodable<APIComponentInActionRow>>>);
// Component management
addComponents(...components: ComponentType[]): this;
setComponents(...components: ComponentType[]): this;
spliceComponents(index: number, deleteCount: number, ...components: ComponentType[]): this;
// Utility methods
toJSON(): APIActionRowComponent<ReturnType<ComponentType['toJSON']>>;
static from<T extends AnyComponentBuilder = AnyComponentBuilder>(
other: JSONEncodable<APIActionRowComponent<ReturnType<T['toJSON']>>> | APIActionRowComponent<ReturnType<T['toJSON']>>
): ActionRowBuilder<T>;
}
/**
* Represents an action row component
* @template ComponentType The type of components this action row contains
*/
class ActionRow<ComponentType extends MessageActionRowComponent | ModalActionRowComponent> {
readonly type: ComponentType.ActionRow;
readonly components: ComponentType[];
toJSON(): APIActionRowComponent<ReturnType<ComponentType['toJSON']>>;
}
interface ActionRowData<ComponentType extends JSONEncodable<APIComponentInActionRow> | ActionRowComponentData> {
type: ComponentType.ActionRow;
components: readonly ComponentType[];
}
type MessageActionRowComponent =
| ButtonComponent
| StringSelectMenuComponent
| UserSelectMenuComponent
| RoleSelectMenuComponent
| MentionableSelectMenuComponent
| ChannelSelectMenuComponent;
type ModalActionRowComponent = TextInputComponent;
type MessageActionRowComponentBuilder =
| ButtonBuilder
| StringSelectMenuBuilder
| UserSelectMenuBuilder
| RoleSelectMenuBuilder
| MentionableSelectMenuBuilder
| ChannelSelectMenuBuilder;
type ModalActionRowComponentBuilder = TextInputBuilder;Interactive buttons for message interfaces with various styles and states.
/**
* Builder for creating button components
*/
class ButtonBuilder {
constructor(data?: Partial<ButtonComponentData | APIButtonComponent>);
// Button configuration
setCustomId(customId: string): this;
setLabel(label: string): this;
setStyle(style: ButtonStyle): this;
setEmoji(emoji: ComponentEmojiResolvable): this;
setURL(url: string): this;
setDisabled(disabled?: boolean): this;
// Utility methods
toJSON(): APIButtonComponent;
static from(other: JSONEncodable<APIButtonComponent> | APIButtonComponent): ButtonBuilder;
}
/**
* Represents a button component
*/
class ButtonComponent extends Component<APIButtonComponent> {
readonly type: ComponentType.Button;
readonly style: ButtonStyle;
readonly label: string | null;
readonly emoji: ComponentEmoji | null;
readonly disabled: boolean;
readonly customId: string | null;
readonly url: string | null;
toJSON(): APIButtonComponent;
}
const ButtonStyle: {
Primary: 1; // Blurple button
Secondary: 2; // Grey button
Success: 3; // Green button
Danger: 4; // Red button
Link: 5; // Link button (requires URL)
};
interface ButtonComponentData extends BaseComponentData {
type: ComponentType.Button;
style: ButtonStyle;
label?: string;
emoji?: ComponentEmojiResolvable;
custom_id?: string;
url?: string;
disabled?: boolean;
}
interface ComponentEmoji {
id: Snowflake | null;
name: string | null;
animated?: boolean;
}
type ComponentEmojiResolvable =
| string
| ComponentEmoji
| EmojiIdentifierResolvable;Usage Examples:
// Basic buttons
const primaryButton = new ButtonBuilder()
.setCustomId('primary_action')
.setLabel('Primary Action')
.setStyle(ButtonStyle.Primary);
const secondaryButton = new ButtonBuilder()
.setCustomId('secondary_action')
.setLabel('Secondary')
.setStyle(ButtonStyle.Secondary)
.setEmoji('⚙️');
const linkButton = new ButtonBuilder()
.setLabel('Visit Website')
.setStyle(ButtonStyle.Link)
.setURL('https://discord.js.org');
// Action row with buttons
const buttonRow = new ActionRowBuilder<ButtonBuilder>()
.addComponents(primaryButton, secondaryButton, linkButton);
await interaction.reply({
content: 'Choose an action:',
components: [buttonRow]
});
// Disabled button
const disabledButton = new ButtonBuilder()
.setCustomId('disabled_action')
.setLabel('Disabled')
.setStyle(ButtonStyle.Secondary)
.setDisabled(true);Dropdown menus for user selection with different target types.
/**
* Base class for select menu builders
*/
class SelectMenuBuilder {
constructor(data?: Partial<SelectMenuComponentData>);
// Menu configuration
setCustomId(customId: string): this;
setPlaceholder(placeholder: string): this;
setMinValues(minValues: number): this;
setMaxValues(maxValues: number): this;
setDisabled(disabled?: boolean): this;
toJSON(): APISelectMenuComponent;
}
/**
* Builder for string select menus
*/
class StringSelectMenuBuilder extends SelectMenuBuilder {
// Option management
addOptions(...options: (StringSelectMenuOptionBuilder | APISelectMenuOption)[]): this;
setOptions(...options: (StringSelectMenuOptionBuilder | APISelectMenuOption)[]): this;
spliceOptions(index: number, deleteCount: number, ...options: (StringSelectMenuOptionBuilder | APISelectMenuOption)[]): this;
toJSON(): APIStringSelectComponent;
}
/**
* Builder for user select menus
*/
class UserSelectMenuBuilder extends SelectMenuBuilder {
// Default values for user selection
setDefaultUsers(...users: UserResolvable[]): this;
addDefaultUsers(...users: UserResolvable[]): this;
toJSON(): APIUserSelectComponent;
}
/**
* Builder for role select menus
*/
class RoleSelectMenuBuilder extends SelectMenuBuilder {
setDefaultRoles(...roles: RoleResolvable[]): this;
addDefaultRoles(...roles: RoleResolvable[]): this;
toJSON(): APIRoleSelectComponent;
}
/**
* Builder for channel select menus
*/
class ChannelSelectMenuBuilder extends SelectMenuBuilder {
// Channel type filtering
setChannelTypes(...channelTypes: RestOrArray<ChannelType>): this;
addChannelTypes(...channelTypes: RestOrArray<ChannelType>): this;
setDefaultChannels(...channels: ChannelResolvable[]): this;
addDefaultChannels(...channels: ChannelResolvable[]): this;
toJSON(): APIChannelSelectComponent;
}
/**
* Builder for mentionable select menus (users and roles)
*/
class MentionableSelectMenuBuilder extends SelectMenuBuilder {
setDefaultUsers(...users: UserResolvable[]): this;
addDefaultUsers(...users: UserResolvable[]): this;
setDefaultRoles(...roles: RoleResolvable[]): this;
addDefaultRoles(...roles: RoleResolvable[]): this;
toJSON(): APIMentionableSelectComponent;
}
/**
* Builder for select menu options (string select only)
*/
class StringSelectMenuOptionBuilder {
constructor(data?: Partial<APISelectMenuOption>);
setLabel(label: string): this;
setValue(value: string): this;
setDescription(description: string): this;
setEmoji(emoji: ComponentEmojiResolvable): this;
setDefault(isDefault?: boolean): this;
toJSON(): APISelectMenuOption;
static from(other: JSONEncodable<APISelectMenuOption> | APISelectMenuOption): StringSelectMenuOptionBuilder;
}Usage Examples:
// String select menu with options
const stringSelect = new StringSelectMenuBuilder()
.setCustomId('music_genre')
.setPlaceholder('Choose a music genre')
.setMinValues(1)
.setMaxValues(3)
.addOptions(
new StringSelectMenuOptionBuilder()
.setLabel('Rock')
.setValue('rock')
.setDescription('Rock music')
.setEmoji('🎸'),
new StringSelectMenuOptionBuilder()
.setLabel('Jazz')
.setValue('jazz')
.setDescription('Jazz music')
.setEmoji('🎷'),
new StringSelectMenuOptionBuilder()
.setLabel('Classical')
.setValue('classical')
.setDescription('Classical music')
.setEmoji('🎻')
);
// User select menu
const userSelect = new UserSelectMenuBuilder()
.setCustomId('select_moderators')
.setPlaceholder('Select moderators')
.setMinValues(1)
.setMaxValues(5);
// Role select menu
const roleSelect = new RoleSelectMenuBuilder()
.setCustomId('assign_roles')
.setPlaceholder('Choose roles to assign')
.setMaxValues(10);
// Channel select menu with type filtering
const channelSelect = new ChannelSelectMenuBuilder()
.setCustomId('select_channel')
.setPlaceholder('Choose a text channel')
.setChannelTypes(ChannelType.GuildText, ChannelType.GuildNews)
.setMaxValues(1);
// Create message with select menus
const selectRow1 = new ActionRowBuilder<StringSelectMenuBuilder>()
.addComponents(stringSelect);
const selectRow2 = new ActionRowBuilder<UserSelectMenuBuilder>()
.addComponents(userSelect);
await interaction.reply({
content: 'Make your selections:',
components: [selectRow1, selectRow2]
});Pop-up forms for collecting user input with text fields.
/**
* Builder for creating modals
*/
class ModalBuilder {
constructor(data?: Partial<ModalData>);
// Modal configuration
setCustomId(customId: string): this;
setTitle(title: string): this;
addComponents(...components: ActionRowBuilder<ModalActionRowComponentBuilder>[]): this;
setComponents(...components: ActionRowBuilder<ModalActionRowComponentBuilder>[]): this;
spliceComponents(index: number, deleteCount: number, ...components: ActionRowBuilder<ModalActionRowComponentBuilder>[]): this;
toJSON(): APIModalInteractionResponseCallbackData;
static from(other: JSONEncodable<APIModalInteractionResponseCallbackData> | APIModalInteractionResponseCallbackData): ModalBuilder;
}
/**
* Builder for text input components
*/
class TextInputBuilder {
constructor(data?: Partial<TextInputComponentData | APITextInputComponent>);
// Input configuration
setCustomId(customId: string): this;
setLabel(label: string): this;
setStyle(style: TextInputStyle): this;
setPlaceholder(placeholder: string): this;
setValue(value: string): this;
setRequired(required?: boolean): this;
setMinLength(minLength: number): this;
setMaxLength(maxLength: number): this;
toJSON(): APITextInputComponent;
static from(other: JSONEncodable<APITextInputComponent> | APITextInputComponent): TextInputBuilder;
}
/**
* Represents a text input component
*/
class TextInputComponent extends Component<APITextInputComponent> {
readonly type: ComponentType.TextInput;
readonly customId: string;
readonly style: TextInputStyle;
readonly label: string;
readonly minLength: number | null;
readonly maxLength: number | null;
readonly required: boolean;
readonly value: string | null;
readonly placeholder: string | null;
toJSON(): APITextInputComponent;
}
const TextInputStyle: {
Short: 1; // Single-line input
Paragraph: 2; // Multi-line input
};
interface ModalData {
title: string;
custom_id: string;
components: ActionRowData<ModalActionRowComponentData>[];
}
interface TextInputComponentData extends BaseComponentData {
type: ComponentType.TextInput;
custom_id: string;
style: TextInputStyle;
label: string;
min_length?: number;
max_length?: number;
required?: boolean;
value?: string;
placeholder?: string;
}Usage Examples:
// Create a feedback modal
const feedbackModal = new ModalBuilder()
.setCustomId('feedback_modal')
.setTitle('Feedback Form')
.addComponents(
new ActionRowBuilder<TextInputBuilder>()
.addComponents(
new TextInputBuilder()
.setCustomId('feedback_title')
.setLabel('Title')
.setStyle(TextInputStyle.Short)
.setPlaceholder('Brief title for your feedback')
.setRequired(true)
.setMaxLength(100)
),
new ActionRowBuilder<TextInputBuilder>()
.addComponents(
new TextInputBuilder()
.setCustomId('feedback_description')
.setLabel('Description')
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder('Detailed description of your feedback...')
.setRequired(true)
.setMaxLength(2000)
),
new ActionRowBuilder<TextInputBuilder>()
.addComponents(
new TextInputBuilder()
.setCustomId('feedback_rating')
.setLabel('Rating (1-10)')
.setStyle(TextInputStyle.Short)
.setPlaceholder('Rate from 1 to 10')
.setRequired(false)
.setMinLength(1)
.setMaxLength(2)
)
);
// Show modal in response to button interaction
if (interaction.isButton() && interaction.customId === 'show_feedback') {
await interaction.showModal(feedbackModal);
}
// Handle modal submission
if (interaction.isModalSubmit() && interaction.customId === 'feedback_modal') {
const title = interaction.fields.getTextInputValue('feedback_title');
const description = interaction.fields.getTextInputValue('feedback_description');
const rating = interaction.fields.getTextInputValue('feedback_rating') || 'Not provided';
await interaction.reply({
content: `Thank you for your feedback!\nTitle: ${title}\nRating: ${rating}`,
ephemeral: true
});
}Additional component features and utilities for complex interfaces.
/**
* Base component class
* @template DataType The API component data type
*/
class Component<DataType extends APIComponentInMessageActionRow | APIComponentInModalActionRow = APIComponentInMessageActionRow | APIComponentInModalActionRow> {
constructor(data: DataType);
readonly type: ComponentType;
toJSON(): DataType;
valueOf(): string;
}
/**
* Container component for organizing UI elements
*/
class ContainerComponent extends Component<APIContainerComponent> {
readonly type: ComponentType.Container;
readonly components: APIComponentInContainer[];
toJSON(): APIContainerComponent;
}
/**
* Section component for grouping related elements
*/
class SectionComponent extends Component<APISectionComponent> {
readonly type: ComponentType.Section;
readonly components: APIComponentInContainer[];
toJSON(): APISectionComponent;
}
/**
* Separator component for visual separation
*/
class SeparatorComponent extends Component<APISeparatorComponent> {
readonly type: ComponentType.Separator;
readonly spacing: SeparatorSpacingSize;
toJSON(): APISeparatorComponent;
}
/**
* Text display component for formatted text
*/
class TextDisplayComponent extends Component<APITextDisplayComponent> {
readonly type: ComponentType.TextDisplay;
readonly content: string;
toJSON(): APITextDisplayComponent;
}
const ComponentType: {
ActionRow: 1;
Button: 2;
StringSelect: 3;
TextInput: 4;
UserSelect: 5;
RoleSelect: 6;
MentionableSelect: 7;
ChannelSelect: 8;
Container: 9;
Section: 10;
Separator: 11;
TextDisplay: 12;
// ... more component types
};interface BaseComponentData {
type: ComponentType;
}
type AnyComponentBuilder =
| ActionRowBuilder<any>
| ButtonBuilder
| StringSelectMenuBuilder
| UserSelectMenuBuilder
| RoleSelectMenuBuilder
| MentionableSelectMenuBuilder
| ChannelSelectMenuBuilder
| TextInputBuilder;
type MessageActionRowComponentData =
| ButtonComponentData
| StringSelectMenuComponentData
| UserSelectMenuComponentData
| RoleSelectMenuComponentData
| MentionableSelectMenuComponentData
| ChannelSelectMenuComponentData;
type ModalActionRowComponentData = TextInputComponentData;
type ActionRowComponentData = MessageActionRowComponentData | ModalActionRowComponentData;
// Component resolvers for finding components by custom ID
interface ComponentResolver {
resolveComponent(customId: string): MessageActionRowComponent | null;
resolveComponents(customId?: string): MessageActionRowComponent[];
}Advanced Usage Examples:
// Complex multi-row interface
const createComplexInterface = () => {
const buttonRow = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId('action_1')
.setLabel('Action 1')
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId('action_2')
.setLabel('Action 2')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('cancel')
.setLabel('Cancel')
.setStyle(ButtonStyle.Danger)
);
const selectRow = new ActionRowBuilder<StringSelectMenuBuilder>()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId('category_select')
.setPlaceholder('Choose a category')
.addOptions(
{ label: 'General', value: 'general', description: 'General category' },
{ label: 'Music', value: 'music', description: 'Music related' },
{ label: 'Gaming', value: 'gaming', description: 'Gaming content' }
)
);
const userSelectRow = new ActionRowBuilder<UserSelectMenuBuilder>()
.addComponents(
new UserSelectMenuBuilder()
.setCustomId('user_select')
.setPlaceholder('Select users')
.setMaxValues(5)
);
return [buttonRow, selectRow, userSelectRow];
};
// Dynamic component updating
const updateComponentState = async (interaction: ButtonInteraction) => {
const components = interaction.message.components;
// Find and update button states
const updatedComponents = components.map(row => {
const newRow = new ActionRowBuilder<ButtonBuilder>();
row.components.forEach(component => {
if (component.type === ComponentType.Button) {
const button = ButtonBuilder.from(component);
// Disable the clicked button
if (component.customId === interaction.customId) {
button.setDisabled(true);
}
newRow.addComponents(button);
}
});
return newRow;
});
await interaction.update({
content: 'Button clicked!',
components: updatedComponents
});
};
// Component validation
const validateComponents = (components: ActionRowBuilder[]) => {
components.forEach((row, rowIndex) => {
if (row.components.length > 5) {
throw new Error(`Row ${rowIndex} has too many components (max 5)`);
}
const buttonCount = row.components.filter(c => c instanceof ButtonBuilder).length;
const selectCount = row.components.filter(c =>
c instanceof StringSelectMenuBuilder ||
c instanceof UserSelectMenuBuilder ||
c instanceof RoleSelectMenuBuilder ||
c instanceof ChannelSelectMenuBuilder ||
c instanceof MentionableSelectMenuBuilder
).length;
if (buttonCount > 0 && selectCount > 0) {
throw new Error(`Row ${rowIndex} cannot mix buttons and select menus`);
}
if (selectCount > 1) {
throw new Error(`Row ${rowIndex} cannot have multiple select menus`);
}
});
};Essential builders for attachments and select menu options.
/**
* Builder for message attachments
*/
class AttachmentBuilder {
constructor(attachment: BufferResolvable | Stream, options?: AttachmentData);
// Set attachment metadata
setName(name: string): this;
setDescription(description: string): this;
setSpoiler(spoiler?: boolean): this;
// Static methods
static from(other: JSONEncodable<AttachmentPayload> | BufferResolvable | Stream): AttachmentBuilder;
// Properties
readonly attachment: BufferResolvable | Stream;
readonly name: string | undefined;
readonly description: string | undefined;
readonly spoiler: boolean;
}
/**
* Base builder for select menu options
*/
class SelectMenuOptionBuilder {
constructor(data?: Partial<APISelectMenuOption>);
// Option configuration
setLabel(label: string): this;
setValue(value: string): this;
setDescription(description?: string): this;
setEmoji(emoji: ComponentEmojiResolvable): this;
setDefault(isDefault?: boolean): this;
// Static methods
static from(other: JSONEncodable<APISelectMenuOption> | APISelectMenuOption): SelectMenuOptionBuilder;
// Convert to API format
toJSON(): APISelectMenuOption;
}
interface AttachmentData {
name?: string;
description?: string;
}
interface AttachmentPayload {
id: number;
filename: string;
description?: string;
content_type?: string;
size: number;
url: string;
proxy_url: string;
height?: number | null;
width?: number | null;
ephemeral?: boolean;
}Usage Examples:
// Create attachment builders
const imageAttachment = new AttachmentBuilder('./path/to/image.png', {
name: 'discord-logo.png',
description: 'The Discord logo'
});
const spoilerAttachment = new AttachmentBuilder(buffer)
.setName('secret.txt')
.setSpoiler(true)
.setDescription('Confidential information');
// Send message with attachments
await channel.send({
content: 'Here are some files!',
files: [imageAttachment, spoilerAttachment]
});
// Create generic select menu options
const genericOption = new SelectMenuOptionBuilder()
.setLabel('Generic Option')
.setValue('generic_value')
.setDescription('A generic select menu option')
.setEmoji('🔧');
// Use in string select menu
const stringSelect = new StringSelectMenuBuilder()
.setCustomId('generic_select')
.setPlaceholder('Choose an option')
.addOptions(
SelectMenuOptionBuilder.from({
label: 'Option 1',
value: 'opt1',
description: 'First option'
}),
genericOption
);Install with Tessl CLI
npx tessl i tessl/npm-discord-js