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
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
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: truestarts a new row. - Author and footer take an object in v14.
setFooter("text")is version 13 code and stops withExpected 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 incontentas well.
Colours
.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://:
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
await interaction.reply({ embeds: [first, second, third] }); // up to 10To change an embed after replying, change the builder and send it again:
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:
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
| Part | Limit |
|---|---|
| Title | 256 characters |
| Description | 4,096 characters |
| Fields | 25 per embed |
| Field name | 256 characters |
| Field value | 1,024 characters |
| Footer text | 2,048 characters |
| Author name | 256 characters |
| All of the above, across every embed in the message | 6,000 characters |
| Embeds per message | 10 |
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 Bodywithembeds[0].fields[0].value[BASE_TYPE_REQUIRED]: This field is requiredunder 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
httporhttps, not a web page that shows it. For uploaded files, theattachment://name must match the attachment'sname. MessageEmbed is not a constructororsetFootercomplaining about a string: version 13 code on version 14. UseEmbedBuilderand 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, theninteraction.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.