Only show guides for your app
Every part of a Discord embed in one place — author, fields, images, footer — with the exact character limits that cause "Invalid Form Body" when you cross them.
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 |
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);
| 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.
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.
interaction.reply({ embeds: [a, b, c] }) posts up to ten embeds in one message.components: [row] alongside embeds to put an action row under the card — see Self-assign roles with buttons.baseEmbed() that sets your brand colour and footer, then .setTitle()/.setDescription() per message so every embed looks consistent.message.edit({ embeds: [updated] }) — the basis of paginated embeds.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."#hex" string. Use the number form 0x5865f2, or parseInt('5865f2', 16).content.setImage/setThumbnail need a public URL Discord can reach; a local file path won't load. To attach a local file, use AttachmentBuilder and reference it as attachment://filename.