Embeds deep dive

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

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] alongside embeds to 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 form 0x5865f2, or parseInt('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 showsetImage/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.

Next steps

Was this guide helpful?