Embeds in discord.js

An embed is the boxed message with a coloured bar down the side. In discord.js 14 you build one with EmbedBuilder. Here is every part of one, how to send and edit them, and what both discord.js and Discord refuse.

8 min readUpdated

This is for discord.js 14 (npm ls discord.js to check). Version 13's MessageEmbed is gone: if you see MessageEmbed is not a constructor, the code was written for 13, and the v14 upgrade guide covers the rest of what moved.

The smallest embed

JavaScript
const { Colors, EmbedBuilder } = require("discord.js");

if (interaction.commandName === "info") {
  const embed = new EmbedBuilder()
    .setTitle("Hello")
    .setDescription("This is an embed.")
    .setColor(Colors.Blurple);
  await interaction.reply({ embeds: [embed] });
}

It is always embeds, a list, even for one. In a prefix command it is the same object: await message.reply({ embeds: [embed] }).

Every part, in one example

JavaScript
const embed = new EmbedBuilder()
  .setTitle("Server report")
  .setURL("https://example.com/report")            // makes the title a link
  .setDescription("Everything from the last **24 hours**.")
  .setColor("#2ecc71")
  .setAuthor({ name: interaction.user.displayName, iconURL: interaction.user.displayAvatarURL() })
  .setThumbnail("https://example.com/logo.png")   // small, top right
  .addFields(
    { name: "New members", value: "12", inline: true },  // side by side
    { name: "Messages", value: "3,406", inline: true },
    { name: "Busiest channel", value: "#general" },      // a row of its own
  )
  .setImage("https://example.com/chart.png")       // large, at the bottom
  .setFooter({ text: "Updated every hour" })
  .setTimestamp();                                 // now, in each reader's time zone
  • Top to bottom it reads: author, title, description, fields, image, then the footer and timestamp on one line. The thumbnail sits at the top right.
  • Inline fields go up to three to a row on a wide screen and stack on a phone. A field without inline: true starts a new row.
  • Author and footer take an object in v14. setFooter("text") is version 13 code and stops with Expected the value to be an object, but received string instead.
  • Markdown works in the description and field values: bold, lists, [masked links](https://example.com).
  • Mentions such as <@user_id> show as mentions inside an embed but never notify anyone. If someone should get pinged, put the mention in content as well.

Colours

JavaScript
.setColor(Colors.Blurple)       // Discord's own blue; also Colors.Red, Colors.Gold and more
.setColor("Red")                // the same names as strings
.setColor("#5865F2")            // hex
.setColor([88, 101, 242])       // red, green, blue
.setColor(0x5865f2)             // a plain number
.setColor("Random")

Images from your own files

An image does not have to be online already. Send it with the message and point the embed at it with attachment://:

JavaScript
const { AttachmentBuilder, EmbedBuilder } = require("discord.js");

const file = new AttachmentBuilder("charts/today.png", { name: "chart.png" });
const embed = new EmbedBuilder().setTitle("Today").setImage("attachment://chart.png");
await interaction.reply({ embeds: [embed], files: [file] });

The name after attachment:// has to be exactly the name given to the attachment. Keep it to letters, numbers and dashes: Discord changes some characters in uploaded file names, and then the two no longer match and the picture shows up as a separate file instead of inside the embed. An image made in memory works the same way: pass a Buffer instead of a path.

Timestamps

.setTimestamp() puts the current time in the footer, and .setTimestamp(date) any other; every reader sees it in their own time zone. For a time inside the text, time(date, TimestampStyles.RelativeTime) (both from discord.js) gives Discord's timestamp format, shown as "in 2 hours" and counting down live.

Several embeds, and editing one

JavaScript
await interaction.reply({ embeds: [first, second, third] });   // up to 10

To change an embed after replying, change the builder and send it again:

JavaScript
const embed = new EmbedBuilder().setTitle("Countdown").setDescription("5");
await interaction.reply({ embeds: [embed] });
for (let n = 4; n >= 0; n--) {
  await new Promise((r) => setTimeout(r, 1000));
  embed.setDescription(n ? String(n) : "Lift off");
  await interaction.editReply({ embeds: [embed] });
}

For an ordinary message, keep what send() returns and call message.edit({ embeds: [embed] }). To change an embed that is already in a channel, start from a copy of it: const embed = EmbedBuilder.from(message.embeds[0]). spliceFields(0, 1, newField) replaces a field, and setFields(...) replaces them all.

Embeds from JSON

An EmbedBuilder can start from the same JSON Discord itself uses, which is handy for messages kept in a config file or made in an online embed builder:

JavaScript
const rules = new EmbedBuilder({
  title: "Rules",
  description: "Be kind. No spam.",
  color: 0x5865f2,
  fields: [{ name: "Appeals", value: "Message a moderator", inline: false }],
});

embed.toJSON() goes the other way, to save one.

The limits

PartLimit
Title256 characters
Description4,096 characters
Fields25 per embed
Field name256 characters
Field value1,024 characters
Footer text2,048 characters
Author name256 characters
All of the above, across every embed in the message6,000 characters
Embeds per message10

discord.js checks some of these itself, before anything is sent: a title over 256 characters throws CombinedError: Received one or more errors at setTitle, and a 26th field throws ExpectedConstraintError. The 6,000 total is Discord's to enforce, so check it yourself with embedLength(embed.toJSON()) (from discord.js) and split long lists across several embeds or pages.

When an embed does not show up

  • The bot is missing Embed Links in that channel. Without it, the embed does not appear. Check the channel's own permission overrides as well as the bot's role.
  • DiscordAPIError[50035]: Invalid Form Body with embeds[0].fields[0].value[BASE_TYPE_REQUIRED]: This field is required under it: a field value is an empty string, which discord.js lets through and Discord does not. Usually a list joined with nothing in it; give it a fallback: value: names.join(", ") || "Nobody yet".
  • The image is missing: the URL has to be the image itself over http or https, not a web page that shows it. For uploaded files, the attachment:// name must match the attachment's name.
  • MessageEmbed is not a constructor or setFooter complaining about a string: version 13 code on version 14. Use EmbedBuilder and the object forms above.
  • The command says The application did not respond: building the embed took more than three seconds (an API call, a database). await interaction.deferReply() first, then interaction.editReply({ embeds: [embed] }). More in slash commands in discord.js.

New to discord.js? Make a Discord bot in JavaScript starts from nothing. When the bot is ready to stay online, SnowServers runs discord.js bots from $3 a month with a 7-day free trial.

Something here wrong or out of date? Tell us and it gets fixed.