Repository navigation
Components v2
This page explains how to work with Discord Components V2 through the internal Components API.
Components V2 messages can be created in two ways:
- Discord's official component JSON format.
- Athena's custom, easier-to-write JSON format.
Both formats support Athena placeholders. The important difference is that the official JSON format is only resolved, while the custom format is resolved, validated, and converted into Discord component JSON.
ㅤ
Discord's official component format uses numeric component type values:
const component = {
type: 17, // Container
components: [
{
type: 10, // Text Display
content: 'Hello %user_mention%! We are testing our %test% & %userId% placeholder!',
},
{
type: 1, // Action Row
components: [
{
type: 2, // Button
custom_id: 'rules',
label: 'Rules',
style: 1, // Primary
},
],
},
],
};Use .resolveComponent() to resolve placeholders in an official Discord component object:
const placeholders = {
'test': 'Placeholder 1',
'userId': interaction.user.id,
}
const components = this.heart.core.util.discord.resolveComponent(component, placeholders, interaction.guild, interaction.user);
await interaction.reply({ components: [components], flags: MessageFlags.IsComponentsV2 });The four parameters are:
-
component: An object using Discord's official component JSON format. -
placeholder: An object containing custom placeholder names and values. -
guild: The Discord guild instance, ornull. -
user: The Discord user or member instance, ornull.
ㅤ
The official component object is not validated by resolveComponent(). It is only copied and resolved. Discord validates the resulting object when the message is sent. The function also does not add the Components V2 message flag; add the flag to the Discord reply or send call yourself.
For multiple top-level components, resolve each component:
const components = componentList.map(component =>
this.heart.core.util.discord.resolveComponent(component, placeholders, interaction.guild, interaction.user),
);
await interaction.reply({ components, flags: MessageFlags.IsComponentsV2 });ㅤ
Athena also provides a custom JSON format. It uses readable string names instead of Discord's numeric type values and is validated before it is converted into Discord component JSON.
const componentConfiguration = {
components: [
{
type: 'container',
components: [
{
type: 'text',
content: 'Welcome to %guild%!',
},
{
type: 'separator',
divider: true,
spacing: 'small',
},
{
type: 'action-row',
components: [
{
type: 'button',
label: 'Open website',
url: '%website_url%',
},
],
},
],
},
],
};The custom format has a top-level components array. Supported component types are:
containeraction-rowsectiontextmedia-galleryseparator
Warning: The custom JSON format only supports URL buttons. It does not support interactive buttons with custom IDs or other action-row items that are not URL buttons. Use the official Discord JSON format with
.resolveComponent()when you need those components.
ㅤ
{
type: 'container',
accentColor: '#2C2F33', // Optional #RRGGBB color or integer
spoiler: false, // Optional boolean
components: [], // One to ten child components
}Containers can contain action rows, sections, text displays, media galleries, and separators.
{
type: 'text',
content: 'This is a text display.',
}{
type: 'action-row',
components: [
{
type: 'button',
label: 'Open website',
url: 'https://example.com',
},
],
}Action rows contain one to five link buttons. The custom builder currently creates these as Discord link buttons.
{
type: 'section',
text: [
'This section contains text.',
'A section can contain up to three text entries.',
],
accessory: {
type: 'thumbnail',
url: 'https://example.com/image.png',
description: 'Optional image description',
spoiler: false,
},
}A section requires one to three text entries and an accessory. An accessory can be a thumbnail or a link button.
{
type: 'media-gallery',
items: [
{
url: 'https://example.com/image.png',
description: 'Optional image description',
spoiler: false,
},
],
}Media galleries contain one to ten image items.
{
type: 'separator',
divider: true,
spacing: 'small', // "small" or "large"
}ㅤ
Use .resolveComponents() for the custom format:
const placeholders = {
'website_url': 'https://example.com',
};
const components = this.heart.core.util.discord.resolveComponents(componentConfiguration, placeholders, interaction.guild, interaction.user);
if (components.valid === false) {
this.heart.core.console.log(this.heart.core.console.type.warn, components.errors.join('\n'));
return;
}
await interaction.reply({ components, flags: MessageFlags.IsComponentsV2 });The top-level components array can contain multiple component objects. For example, a message can contain two separate containers:
const componentConfiguration = {
components: [
{
type: 'container',
components: [
{
type: 'text',
content: 'This is the first component.',
},
],
},
{
type: 'container',
components: [
{
type: 'text',
content: 'This is the second component.',
},
],
},
],
};
const components = this.heart.core.util.discord.resolveComponents(componentConfiguration, {}, interaction.guild, interaction.user,);
await interaction.reply({ components, flags: MessageFlags.IsComponentsV2 });Each object in the top-level array is validated and converted independently, and the returned array can be passed directly to Discord.
On success, the function returns an array containing official Discord component JSON objects. It returns only the components; it does not return the MessageFlags.IsComponentsV2 flag.
When validation fails, it returns:
{
valid: false,
errors: [
'message.components[0].components must contain between 1 and 10 items',
],
}If the configuration is invalid, do not send the result to Discord. Check valid === false before using it.
The custom configuration is validated for supported keys, component types, required properties, array sizes, text lengths, URLs, colors, and accessory types. Placeholder values are resolved before validation.
ㅤ
Both .resolveComponent() and .resolveComponents() use the same placeholder resolver:
-
%custom_emoji_<number>%- A custom Discord emoji -
%user%- Username -
%user_display%- Display name -
%user_id%- User ID -
%user_mention%- User mention -
%user_icon%- User avatar URL -
%user_created%- User creation date -
%user_created_formatted%- User creation date as a Discord timestamp -
%user_joined%- User join date -
%user_joined_formatted%- User join date as a Discord timestamp -
%guild%- Guild name -
%guild_name%- Guild name -
%guild_id%- Guild ID -
%guild_member_count%- Guild member count -
%guild_icon%- Guild icon URL -
%random_int_<max_number>%- Random integer -
%random_string_<length>%- Random string -
%current_date%- Current date (YYYY-MM-DD) -
%current_time%- Current time (HH:mm) -
%current_time_seconds%- Current time (HH:mm:ss) -
%current_datetime%- Current date and time (YYYY-MM-DD HH:mm) -
%current_iso%- Current date in ISO format -
%channel_<id>%- Channel mention -
%user_<id>%- User mention -
%role_<id>%- Role mention -
%bot_user_mention%- Bot user mention -
%bot_user_id%- Bot user ID -
%bot_user%- Bot username -
%bot_name%- Bot username -
%bot_user_icon%- Bot avatar URL
Configured global placeholders are also resolved automatically.
(!) User placeholders require a user instance, and guild placeholders require a guild instance. If either instance is unavailable, pass null. Values supplied in the placeholder object override default placeholders with the same name.
AthenaBot Developer Documentation