Skip to content

Discord Message Components V2

Since 2025, message components got a lot more powerful, allowing for more interactive and dynamic user experiences. This document outlines the key features and improvements introduced in this version.

  • Layout Components - For organizing and structuring content (Action Rows, Sections, Containers)
  • Content Components - For displaying static text, images, and files (Text Display, Media Gallery, Thumbnails)
  • Interactive Components - For user interactions (Buttons, Select Menus, Text Input)

The following is a complete table of available message components. Details about each component are in the sections below.

TypeNameDescriptionStyle
1Action RowContainer to display a row of interactive componentsLayout
2ButtonButton objectInteractive
3String SelectSelect menu for picking from defined text optionsInteractive
5User SelectSelect menu for usersInteractive
6Role SelectSelect menu for rolesInteractive
7Mentionable SelectSelect menu for mentionables (users and roles)Interactive
8Channel SelectSelect menu for channelsInteractive
9SectionContainer to display text alongside an accessory componentLayout
10Text DisplayMarkdown textContent
11ThumbnailSmall image that can be used as an accessoryContent
12Media GalleryDisplay images and other mediaContent
13FileDisplays an attached fileContent
14SeparatorComponent to add vertical padding between other componentsLayout
17ContainerContainer that visually groups a set of componentsLayout

All components have the following fields:

  • type (integer) | The type of the component
  • id (integer, optional) | 32 bit integer used as an optional identifier for component

The id field is optional and is used to identify components in the response from an interaction. The id must be unique within the message and is generated sequentially if left empty. Generation of ids won’t use another id that exists in the message if you have one defined for another component. Sending components with an id of 0 is allowed but will be treated as empty and replaced by the API.

Additionally, interactive components like buttons and selects must have a custom_id field. The developer defines this field when sending the component payload, and it is returned in the interaction payload sent when a user interacts with the component. For example, if you set custom_id: click_me on a button, you’ll receive an interaction containing custom_id: click_me when a user clicks that button.

custom_id is only available on interactive components and must be unique per component. Multiple components on the same message must not share the same custom_id. Maximum length is 100 characters.


An Action Row is a top-level layout component.

Action Rows can contain one of the following:

FieldTypeDescription
typeinteger1 for action row component
id?integerOptional identifier for component
componentsarray of action row child componentsUp to 5 interactive button components or a single select component
{
"flags": 32768,
"components": [
{
"type": 1, // ComponentType.ACTION_ROW
"components": [
{
"type": 2, // ComponentType.BUTTON
"custom_id": "click_yes",
"label": "Accept",
"style": 1
},
{
"type": 2, // ComponentType.BUTTON
"label": "Learn More",
"style": 5,
"url": "http://watchanimeattheoffice.com/"
},
{
"type": 2, // ComponentType.BUTTON
"custom_id": "click_no",
"label": "Decline",
"style": 4
}
]
}
]
}

A Button is an interactive component that can only be used in messages.

Buttons must be placed inside an Action Row or a Section’s accessory field.

FieldTypeDescription
typeinteger2 for a button
id?integerOptional identifier for component
styleintegerA button style
label?stringText that appears on the button; max 80 characters
emoji?partial emojiname, id, and animated (id and animated are optional)
custom_idstringDeveloper-defined identifier for the button; max 100 characters
sku_id?snowflakeIdentifier for a purchasable SKU (stock keeping unit), only available when using premium-style buttons
url?stringURL for link-style buttons; max 512 characters
disabled?booleanWhether the button is disabled (defaults to false)

Buttons come in various styles to convey different types of actions. These styles also define what fields are valid for a button.

  • Non-link and non-premium buttons must have a custom_id, and cannot have a url or a sku_id.
  • Link buttons must have a url, and cannot have a custom_id
  • Link buttons do not send an interaction to the app when clicked
  • Premium buttons must contain a sku_id, and cannot have a custom_id, label, url, or emoji.
  • Premium buttons do not send an interaction to the app when clicked
NameValueActionRequired Field
Primary1The most important or recommended action in a group of optionscustom_id
Secondary2Alternative or supporting actionscustom_id
Success3Positive confirmation or completion actionscustom_id
Danger4An action with irreversible consequencescustom_id
Link5Navigates to a URLurl
Premium6Purchasesku_id

Just a button:

{
"flags": 32768,
"components": [
{
"type": 1, // ComponentType.ACTION_ROW
"components": [
{
"type": 2, // ComponentType.BUTTON
"custom_id": "click_yes",
"label": "Accept",
"style": 1
}
]
}
]
}

Premium buttons will automatically have the following:

  • Shop Icon
  • SKU name
  • SKU price

A String Select is an interactive component that allows users to select one or more provided options.

String Selects can be configured for both single-select and multi-select behavior.

String Selects must be placed inside an Action Row in messages.

FieldTypeDescription
typeinteger3 for string select
id?integerOptional identifier for component
custom_idstringID for the select menu; max 100 characters
optionsarray of select optionsSpecified choices in a select menu; max 25
placeholder?stringPlaceholder text if nothing is selected or default; max 150 characters
min_values?integerMinimum number of items that must be chosen (defaults to 1); min 0, max 25
max_values?integerMaximum number of items that can be chosen (defaults to 1); max 25
disabled?booleanWhether select menu is disable in a message (defaults to false)
FieldTypeDescription
labelstringUser-facing name of the option; max 100 characters
valuestringDev-defined value of the option; max 100 characters
description?stringAdditional description of the option; max 100 characters
emoji?partial emoji objectid, name, and animated (id and animated are optional)
default?booleanWill show this option as selected by default

Note that the number of options must be within the range of min_values and max_values.

{
"flags": 32768,
"components": [
{
"type": 1, // ComponentType.ACTION_ROW,
"id": 1,
"components": [
{
"type": 3, // ComponentType.STRING_SELECT
"id": 2,
"custom_id": "favorite_bug",
"placeholder": "Favorite bug?",
"options": [
{
"label": "Ant",
"value": "ant",
"description": "(best option)",
"emoji": { "name": "🐜" }
},
{
"label": "Butterfly",
"value": "butterfly",
"emoji": { "name": "🦋" }
},
{
"label": "Caterpillar",
"value": "caterpillar",
"emoji": { "name": "🐛" }
}
]
}
]
}
]
}

A User Select is an interactive component that allows users to select one or more users in a message. Options are automatically populated based on the server’s available users.

User Selects can be configured for both single-select and multi-select behavior.

User Selects must be placed inside an Action Row.

FieldTypeDescription
typeinteger5 for user select
id?integerOptional identifier for component
custom_idstringID for the select menu; max 100 characters
placeholder?stringPlaceholder text if nothing is selected; max 150 characters
default_values?array of default value objectsList of default values; number of default values must be in the range defined by min_values and max_values
min_values?integerMinimum number of items that must be chosen (defaults to 1); min 0, max 25
max_values?integerMaximum number of items that can be chosen (defaults to 1); max 25
disabled?booleanWhether select menu is disabled (defaults to false)
FieldTypeDescription
idsnowflakeID of a user, role, or channel
typestringType of value that id represents. Either "user", "role", or "channel"

Regular user select

{
"flags": 32768,
"components": [
{
"type": 1, // ComponentType.ACTION_ROW
"components": [
{
"type": 5, // ComponentType.USER_SELECT
"custom_id": "user_select",
"placeholder": "Select a user"
}
]
}
]
}

User select with default values

{
"flags": 32768,
"components": [
{
"type": 1, // ComponentType.ACTION_ROW
"components": [
{
"type": 5, // ComponentType.USER_SELECT
"custom_id": "user_select",
"placeholder": "Select a user",
"default_values": [
{
"id": "123456789012345678",
"type": "user"
}
]
}
]
}
]
}

The same as the User Select, but for the roles within a guild.

The type of a role select is 6.

Note, that the default values have to have the type "role".

Regular role select

{
"flags": 32768,
"components": [
{
"type": 1, // ComponentType.ACTION_ROW
"components": [
{
"type": 6, // ComponentType.ROLE_SELECT
"custom_id": "role_select",
"placeholder": "Select a role"
}
]
}
]
}

Role select with default values

{
"flags": 32768,
"components": [
{
"type": 1, // ComponentType.ACTION_ROW
"components": [
{
"type": 6, // ComponentType.ROLE_SELECT
"custom_id": "role_select",
"placeholder": "Select a role",
"default_values": [
{
"id": "123456789012345678",
"type": "role"
}
]
}
]
}
]
}

Mentionable selects are uniting user selects and roles selects.

The type of a mentionable select is 7.

Note that the default values can accept both types "user" and "role".

{
"flags": 32768,
"components": [
{
"type": 1, // ComponentType.ACTION_ROW
"components": [
{
"type": 7, // ComponentType.MENTIONABLE_SELECT
"custom_id": "who_to_ping",
"placeholder": "Who?"
}
]
}
]
}

With default values:

{
"flags": 32768,
"components": [
{
"type": 1, // ComponentType.ACTION_ROW
"components": [
{
"type": 7, // ComponentType.MENTIONABLE_SELECT
"custom_id": "who_to_ping",
"placeholder": "Who?",
"default_values": [
{
"id": "123456789012345678",
"type": "user"
},
{
"id": "987654321098765432",
"type": "role"
}
]
}
]
}
]
}

A Channel Select is an interactive component that allows users to select one or more channels in a message.

Channel Selects can be configured for both single-select and multi-select behavior.

FieldTypeDescription
typeinteger8 for channel select
id?integerOptional identifier for component
custom_idstringID for the select menu; max 100 characters
channel_types?array of channel typesList of channel types to include in the channel select component
placeholder?stringPlaceholder text if nothing is selected; max 150 characters
default_values?array of default value objectsList of default values; number of default values must be in the range defined by min_values and max_values
min_values?integerMinimum number of items that must be chosen (defaults to 1); min 0, max 25
max_values?integerMaximum number of items that can be chosen (defaults to 1); max 25
disabled?booleanWhether select menu is disabled (defaults to false)
  • GUILD_TEXT: 0 - a text channel within a server
  • DM: 1 - a direct message between users
  • GUILD_VOICE: 2 - a voice channel within a server
  • GROUP_DM: 3 - a direct message between multiple users
  • GUILD_CATEGORY: 4 - an organizational category that contains up to 50 channels
  • GUILD_ANNOUNCEMENT: 5 - a channel that users can follow and crosspost into their own server (formerly news channels)
  • ANNOUNCEMENT_THREAD: 10 - a temporary sub-channel within a GUILD_ANNOUNCEMENT channel
  • PUBLIC_THREAD: 11 - a temporary sub-channel within a GUILD_TEXT or GUILD_FORUM channel
  • PRIVATE_THREAD: 12 - a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission
  • GUILD_STAGE_VOICE: 13 - a voice channel for hosting events with an audience
  • GUILD_DIRECTORY: 14 - the channel in a hub containing the listed servers
  • GUILD_FORUM: 15 - channel that can only contain threads
  • GUILD_MEDIA: 16 - channel that can only contain threads, similar to GUILD_FORUM channels

Missing values are legacy channel types.

{
"flags": 32768,
"components": [
{
"type": 1, // ComponentType.ACTION_ROW
"components": [
{
"type": 8, // ComponentType.CHANNEL_SELECT
"custom_id": "notification_channel",
"channel_types": [0], // ChannelType.TEXT
"placeholder": "Which text channel?"
}
]
}
]
}

A Section is a top-level layout component that allows you to contextually associate content with an accessory component. The typical use-case is to contextually associate text content with an accessory.

Sections MUST include the accessory component.

FieldTypeDescription
typeinteger9 for section component
id?integerOptional identifier for component
componentsarray of section child components1 - 3 child components representing the content of the section that is contextually associated to the accessory
accessorysection accessory componentA component that is contextually associated to the content of the section
{
"flags": 32768,
"components": [
{
"type": 9, // ComponentType.SECTION
"components": [
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "The game is out now! Check it out on our website."
}
],
"accessory": {
"type": 11, // ComponentType.THUMBNAIL
"media": {
"url": "https://websitewithopensourceimages/gamepreview.webp"
}
}
}
]
}

A Text Display is a content component that allows you to add markdown formatted text, including mentions (users, roles, etc) and emojis. The behavior of this component is extremely similar to the content field of a message, but allows you to add multiple text components, controlling the layout of your message.

When sent in a message, pingable mentions (@user, @role, etc) present in this component will ping and send notifications based on the value of the allowed mention object set in message.allowed_mentions.

FieldTypeDescription
typeinteger10 for text display
id?integerOptional identifier for component
contentstringText that will be displayed similar to a message
{
"flags": 32768,
"components": [
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "# Real Game v7.3"
},
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "Hope you're excited, the update is finally here! Here are some of the changes:\n- Fixed a bug where certain treasure chests wouldn't open properly\n- Improved server stability during peak hours\n- Added a new type of gravity that will randomly apply when the moon is visible in-game\n- Every third thursday the furniture will scream your darkest secrets to nearby npcs"
},
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "-# That last one wasn't real, but don't use voice chat near furniture just in case..."
}
]
}

TomoriBot renders the leading “title” line of every Components V2 container (status, confirmation, persona picker, persona results, memory/task notices) as a Markdown H2 heading via the shared formatContainerTitle helper in src/utils/discord/ui/interactionCore.ts. Keep title locale strings plain text (an emoji prefix is fine) — do not embed ## or ** in them, or the heading will double up. Body text, section sub-headings, and footers are unaffected.

Memory and scheduled-task notices use buildNoticeContainer in src/utils/discord/ui/interactionCore.ts. The old embed title maps to the H2 title, the old embed description maps to a Text Display, and the old embed footer maps to muted -# subtext after a separator. When the memory/task body is truncated, the Secondary “Expand” button is rendered as an Action Row inside the same container; the ephemeral full-content reveal remains a separate classic embed reply.


A Thumbnail is a content component that displays visual media in a small form-factor. It is intended as an accessory for to other content, and is primarily usable with sections. The media displayed is defined by the unfurled media item structure, which supports both uploaded media and externally hosted media.

Thumbnails currently only support images, including animated formats like GIF and WEBP. Videos are not supported at this time.

FieldTypeDescription
typeinteger11 for thumbnail component
id?integerOptional identifier for component
mediaunfurled media itemA url or attachment provided as an unfurled media item
description?stringAlt text for the media, max 1024 characters
spoiler?booleanWhether the thumbnail should be a spoiler (or blurred out). Defaults to false
{
"flags": 32768,
"components": [
{
"type": 9, // ComponentType.SECTION
"components": [
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "Please visit our website for more information."
}
],
"accessory": {
"type": 11, // ComponentType.THUMBNAIL
"media": {
"url": "https://websitewithopensourceimages/gamepreview.webp"
}
}
}
]
}

A Media Gallery is a top-level content component that allows you to display 1-10 media attachments in an organized gallery format. Each item can have optional descriptions and can be marked as spoilers.

FieldTypeDescription
typeinteger12 for media gallery component
id?integerOptional identifier for component
itemsarray of media gallery items1 to 10 media gallery items
FieldTypeDescription
mediaunfurled media itemA url or attachment provided as an unfurled media item
description?stringAlt text for the media, max 1024 characters
spoiler?booleanWhether the media should be a spoiler. Defaults to false
{
"flags": 32768,
"components": [
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "Live webcam shots as of 18-04-2025 at 12:00 UTC"
},
{
"type": 12, // ComponentType.MEDIA_GALLERY
"items": [
{
"media": {
"url": "https://livevideofeedconvertedtoimage/webcam1.webp"
},
"description": "An aerial view looking down on older industrial complex buildings. The main building is white with many windows and pipes running up the walls."
},
{
"media": {
"url": "https://livevideofeedconvertedtoimage/webcam2.webp"
},
"description": "An aerial view of old broken buildings. Nature has begun to take root in the rooftops. A portion of the middle building's roof has collapsed inward. In the distant haze you can make out a far away city."
},
{
"media": {
"url": "https://livevideofeedconvertedtoimage/webcam3.webp"
},
"description": "A street view of a downtown city. Prominently in photo are skyscrapers and a domed building"
}
]
}
]
}

Generated image tool output uses Components V2 so the timing subtext appears visually below the rendered image instead of above the attachment. The message sends the image as an attachment, exposes that attachment through a Media Gallery item, then follows it with a Text Display using Discord subtext markdown.

{
"flags": 32768,
"components": [
{
"type": 12,
"items": [
{
"media": {
"url": "attachment://generated_1770000000000.png"
}
}
]
},
{
"type": 10,
"content": "-# Generated after 4.2 seconds."
}
]
}

Implementation notes:

  • The attachment filename must exactly match the attachment://<filename> URL.
  • Components V2 messages must set MessageFlags.IsComponentsV2 and must not include content or embeds.
  • Keep a message’s whole edit lifecycle in one mode. If a command will eventually render a Components V2 result, its intermediate processing and terminal error states should also be Components V2; otherwise editReply() can patch a legacy embed/content message into a V2 message and Discord rejects the final payload.
  • In discord.js edits, omitting embeds or content does not reliably clear an existing legacy field before MessageFlags.IsComponentsV2 validation. Prefer sending a V2 status container from the first visible state instead of converting an existing legacy reply in place.
  • Attachments on Components V2 messages do not render unless they are referenced by a component.
  • Webhook sends and edits through discord.js must also pass withComponents: true.
  • Image delivery should fall back to the legacy files-only payload if the Components V2 send fails.
  • Re-fetching a generated image by message/media ID (for image-to-image, inpaint, or vision analysis) must scan message.components for MediaGallery/Thumbnail/File media, not just top-level attachments/embeds — the generated file is referenced only inside the component. This discovery is centralized in collectImageUrlsFromMessage (src/utils/image/imageExtractor.ts), which reuses appendComponentMediaFromMessage from src/utils/chat/contextMedia.ts. generate_image, generate_image_nai, and analyze_image all go through it, so a Components V2 image found in context can also be reloaded by any tool that accepts a media reference.

When modernizing an embed workflow to Components V2, migrate the complete reply flow together:

  • Initial deferred reply edits, “processing” messages, success messages, timeout messages, cancellation messages, and post-processing errors should all use Components V2 components with MessageFlags.IsComponentsV2.
  • Files attached to V2 messages must be referenced by MediaGallery, Thumbnail, or File; an unreferenced attachment is not displayed.
  • Shared helpers such as status containers are preferred for repeated states. Local builders are acceptable for command-specific payloads, but do not mix them with legacy embeds on the same original interaction reply after the V2 state has been sent.

A File is a top-level content component that allows you to display an uploaded file as an attachment to the message and reference it in the component. Each file component can only display 1 attached file, but you can upload multiple files and add them to different file components within your payload.

Info: The File component only supports using the attachment:// protocol in unfurled media item.

FieldTypeDescription
typeinteger13 for a file component
id?integerOptional identifier for component
fileunfurled media itemThis unfurled media item is unique in that it only supports attachment references using the attachment://<filename> syntax
spoiler?booleanWhether the media should be a spoiler (or blurred out). Defaults to false
namestringThe name of the file. This field is ignored and provided by the API as part of the response
sizeintegerThe size of the file in bytes. This field is ignored and provided by the API as part of the response

Info: This example makes use of the attachment:// protocol functionality in unfurled media item.

{
"flags": 32768,
"components": [
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "# New game version released for testing!\nGrab the game here:"
},
{
"type": 13, // ComponentType.FILE
"file": {
"url": "attachment://game.zip"
}
},
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "Latest manual artwork here:"
},
{
"type": 13, // ComponentType.FILE
"file": {
"url": "attachment://manual.pdf"
}
}
]
}

A Separator is a top-level layout component that adds vertical padding and visual division between other components.

FieldTypeDescription
typeinteger14 for separator component
id?integerOptional identifier for component
divider?booleanWhether a visual divider should be displayed in the component. Defaults to true
spacing?integerSize of separator padding - 1 for small padding, 2 for large padding. Defaults to 1
{
"flags": 32768,
"components": [
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "It's dangerous to go alone!"
},
{
"type": 14, // ComponentType.SEPARATOR
"divider": true,
"spacing": 1
},
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "Take this."
}
]
}

A Container is a top-level layout component. Containers offer the ability to visually encapsulate a collection of components and have an optional customizable accent color bar.

FieldTypeDescription
typeinteger17 for container component
id?integerOptional identifier for component
componentsarray of container child componentsChild components that are encapsulated within the Container
accent_color??integerColor for the accent on the container as RGB from 0x000000 to 0xFFFFFF
spoiler?booleanWhether the container should be a spoiler (or blurred out). Defaults to false.
{
"flags": 32768,
"components": [
{
"type": 17, // ComponentType.CONTAINER
"accent_color": 703487,
"components": [
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "# You have encountered a wild coyote!"
},
{
"type": 12, // ComponentType.MEDIA_GALLERY
"items": [
{
"media": {
"url": "https://websitewithopensourceimages/coyote.webp"
}
}
]
},
{
"type": 10, // ComponentType.TEXT_DISPLAY
"content": "What would you like to do?"
},
{
"type": 1, // ComponentType.ACTION_ROW
"components": [
{
"type": 2, // ComponentType.BUTTON
"custom_id": "pet_coyote",
"label": "Pet it!",
"style": 1
},
{
"type": 2, // ComponentType.BUTTON
"custom_id": "feed_coyote",
"label": "Attempt to feed it",
"style": 2
},
{
"type": 2, // ComponentType.BUTTON
"custom_id": "run_away",
"label": "Run away!",
"style": 4
}
]
}
]
}
]
}

An Unfurled Media Item is a piece of media, represented by a URL, that is used within a component. It can be constructed via either uploading media to Discord, or by referencing external media via a direct link to the asset.

Info: While the structure below is the full representation of an Unfurled Media Item, only the url field is settable by developers when making requests that utilize this structure. All other fields will be automatically populated by Discord.

FieldTypeDescription
urlstringSupports arbitrary urls and attachment://<filename> references
proxy_url?stringThe proxied url of the media item. This field is ignored and provided by the API as part of the response
height??integerThe height of the media item. This field is ignored and provided by the API as part of the response
width??integerThe width of the media item. This field is ignored and provided by the API as part of the response
content_type?stringThe media type of the content. This field is ignored and provided by the API as part of the response
attachment_id?*snowflakeThe id of the uploaded attachment. This field is ignored and provided by the API as part of the response

* Only present if the media item was uploaded as an attachment.

To upload a file with your message, you’ll need to send your payload as multipart/form-data (rather than application/json) and include your file with a valid filename in your payload. Details and examples for uploading files can be found in the Discord API Reference.

Before the introduction of the IS_COMPONENTS_V2 flag, message components were sent in conjunction with message content. This means that you could send a message using a subset of the available components without setting the IS_COMPONENTS_V2 flag, and the components would be included in the message content along with content and embeds.

Additionally, components of messages preceding components V2 will contain an id of 0.

Apps using this Legacy Message Component behavior will continue to work as expected, but it is recommended to use the new IS_COMPONENTS_V2 flag for new apps or features as they offer more options for layout and customization.

Info: Legacy messages allow up to 5 action rows as top-level components

Legacy Message Component Example

{
"content": "This is a message with legacy components",
"components": [
{
"type": 1,
"components": [
{
"type": 2,
"style": 1,
"label": "Click Me",
"custom_id": "click_me_1"
}
]
}
]
}