Embeds are the framed, coloured cards that make a bot's replies look finished. Slash commands in depth introduced a simple one; this is the full reference — every field EmbedBuilder can set, what each looks like, and the character limits that quietly break an embed when you exceed them.
| At a glance | |
|---|---|
| You need | a working bot from Host a discord.js bot |
| Plan | Free or premium |
| Time | About fifteen minutes |
A fully-loaded embed
This buildEmbed function uses every part an embed has. Drop it into your bot and call it from a command:
require('dotenv').config();
const {
Client, Events, GatewayIntentBits,
SlashCommandBuilder, EmbedBuilder,
} = require('discord.js');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const show = new SlashCommandBuilder()
.setName('embed')
.setDescription('Show off a fully-loaded embed');
function buildEmbed(user) {
return new EmbedBuilder()
.setColor(0x5865f2) // left bar colour (hex number)
.setTitle('Everything an embed can hold') // up to 256 chars
.setURL('https://falixnodes.net') // makes the title a link
.setAuthor({
name: user.tag, // up to 256 chars
iconURL: user.displayAvatarURL(),
url: 'https://falixnodes.net',
})
.setDescription('The big block of text under the title. Up to **4096** characters, and it supports *Markdown*.')
.setThumbnail(user.displayAvatarURL()) // small top-right image
.addFields( // up to 25 fields
{ name: 'Inline field', value: 'Sits side by side', inline: true },
{ name: 'Another', value: 'Next to it', inline: true },
{ name: 'Full-width field', value: 'Own row (inline omitted)' },
)
.setImage('https://falixnodes.net/img/logo.png') // large image at the bottom
.setFooter({ text: 'Footer text — up to 2048 chars', iconURL: user.displayAvatarURL() })
.setTimestamp(); // adds a time next to the footer
}
client.once(Events.ClientReady, async (c) => {
await c.application.commands.set([show]);
console.log(`Listening as ${c.user.tag}`);
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'embed') {
await interaction.reply({ embeds: [buildEmbed(interaction.user)] });
}
});
client.login(process.env.DISCORD_TOKEN);
What each piece does
| Method | What it controls |
|---|---|
setColor(0x5865f2) |
The coloured bar down the left edge. Takes a hex number (0x…), not a "#…" string |
setTitle('…') |
Bold heading at the top |
setURL('…') |
Turns the title into a clickable link |
setAuthor({ name, iconURL, url }) |
Small name + icon line above the title |
setDescription('…') |
The main body text; supports Markdown |
setThumbnail(url) |
Small image in the top-right corner |
addFields({ name, value, inline }) |
Labelled mini-sections; inline: true places up to three side by side |
setImage(url) |
Large image across the bottom |
setFooter({ text, iconURL }) |
Small line at the very bottom |
setTimestamp() |
Adds a time next to the footer (defaults to now) |
Two small rules worth remembering: setColor wants a number — write 0x5865f2, and if you only have a #5865f2 string, convert it with parseInt('5865f2', 16). And an inline field shares its row with up to two other inline fields; leave inline off for a full-width row.
The limits (this is what breaks embeds)
Discord rejects an embed that exceeds any of these, and discord.js surfaces it as Invalid Form Body. Keep them in mind whenever your text is user-generated:
| Part | Limit |
|---|---|
| Title | 256 characters |
| Description | 4096 characters |
| Fields | 25 maximum |
| Field name | 256 characters |
| Field value | 1024 characters |
| Footer text | 2048 characters |
| Author name | 256 characters |
| Everything combined | 6000 characters across the whole embed |
| Embeds per message | 10 maximum |
⚠️ Heads up: The 6000-character total is the sneaky one — each part can be under its own limit while the embed as a whole is over. When you build an embed from user input (a message, a form answer), truncate long values with something like
value.slice(0, 1024)before adding them, or a single long message crashes the reply.
Making it yours
- Send several at once.
interaction.reply({ embeds: [a, b, c] })posts up to ten embeds in one message. - Pair it with buttons. Add
components: [row]alongsideembedsto put an action row under the card — see Self-assign roles with buttons. - Reuse a template. Keep a
baseEmbed()that sets your brand colour and footer, then.setTitle()/.setDescription()per message so every embed looks consistent. - Edit later. Store the message and call
message.edit({ embeds: [updated] })— the basis of paginated embeds.
Troubleshooting
Invalid Form Body/BASE_TYPE_MAX_LENGTH— a field or the whole embed is over a limit in the table above. Truncate user-supplied text before adding it.- The color is wrong or throws — you passed a
"#hex"string. Use the number form0x5865f2, orparseInt('5865f2', 16). - "Cannot send an empty message" — an embed with no title, description, or fields counts as empty. Give it at least one of those, or add
content. - An image doesn't show —
setImage/setThumbnailneed a public URL Discord can reach; a local file path won't load. To attach a local file, useAttachmentBuilderand reference it asattachment://filename.