---
title: "Discord embeds with discord.js: how to build formatted messages | StreetHosting"
description: "Learn to build embeds in discord.js v14 with EmbedBuilder: color, author, image, fields, footer and timestamp, plus the size limits and how to edit an embed."
url: "https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages"
type: "page"
language: "en-US"
---

Discord Bots · 7 min · Beginner

Published on Nov 13, 2026 · Updated on Nov 13, 2026

# Discord embeds: how to build formatted messages with discord.js

The embed is the colored card that gives your bot's messages a professional look. Learn to build one from scratch, the official limits, what markdown works inside it and how to edit an embed that was already sent.

By [Equipe StreetHosting](https://streethosting.com.br/en/autores#equipe-streethosting) · StreetHosting infrastructure and support team

[Automation and webhooks](https://streethosting.com.br/en/guides/topics/automation)

Summarize with:

[](https://chat.openai.com/?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscordjs-embed-formatted-messages.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "ChatGPT") [](https://claude.ai/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscordjs-embed-formatted-messages.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Claude") [](https://www.google.com/search?udm=50&aep=11&q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscordjs-embed-formatted-messages.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Google AI Mode") [](https://x.com/i/grok?text=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscordjs-embed-formatted-messages.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Grok") [](https://www.perplexity.ai/search/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscordjs-embed-formatted-messages.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Perplexity")

Share:

[](https://x.com/intent/tweet?text=Discord%20embeds%20with%20discord.js%3A%20how%20to%20build%20formatted%20messages&url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscordjs-embed-formatted-messages "Share on X") [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscordjs-embed-formatted-messages "Share on Facebook") [](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscordjs-embed-formatted-messages "Share on LinkedIn") [](https://wa.me/?text=Discord%20embeds%20with%20discord.js%3A%20how%20to%20build%20formatted%20messages%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscordjs-embed-formatted-messages "Share on WhatsApp")

For agents: Copy as Markdown [.md](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages.md)

In this guide 7 sections

* [Your first embed](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#primeiro-embed)
* [Author, thumbnail, image, footer and timestamp](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#partes-do-embed)
* [Fields and inline columns](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#campos)
* [Official size limits](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#limites)
* [Markdown inside the embed](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#markdown)
* [Edit an existing embed](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#editar-embed)
* [Where to keep the bot running](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#onde-rodar)

Quick answer

In discord.js v14 you build the embed with `new EmbedBuilder()`, chain `setTitle`, `setColor`, `setDescription` and `addFields` and send it with `{ embeds: [embed] }`. Stay inside the limits: a 256 character title, a 4096 character description, 25 fields and 6000 characters added up across the message.

## Your first embed[](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#primeiro-embed)

An embed is a block of content with a colored bar on its side, which Discord draws below (or in place of) the message text. It works for announcements, profiles, status panels, rules and anything that needs more organization than a loose line of text. Only bots and webhooks can send embeds, so it also makes it clear that the message came from a system.

Let's build a command that shows information about the server. Assuming the `/server`command is already registered (if it isn't, see [how to create slash commands](https://streethosting.com.br/en/guides/discord-bots/create-discord-bot-slash-commands)), the handler looks like this:

`const { EmbedBuilder, Colors } = require('discord.js'); client.on('interactionCreate', async (interaction) => { if (!interaction.isChatInputCommand()) return; if (interaction.commandName !== 'server') return; const embed = new EmbedBuilder() .setColor(Colors.Blurple) .setTitle('Server information') .setDescription('Quick summary of **' + interaction.guild.name + '**.'); await interaction.reply({ embeds: [embed] }); });`

The color accepts the constants from `Colors`, a hexadecimal number like `0xff7a00` or a string like `'#ff7a00'`. If you don't set a color, Discord uses a dark gray bar.

The bot needs the **Embed Links**permission in the channel. Without it the message arrives with the text only, or doesn't arrive, and the embed disappears with no clear warning. Also check that the bot's role isn't blocked in the channel by a specific permission override.

## Author, thumbnail, image, footer and timestamp[](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#partes-do-embed)

Besides title and description, the embed has five areas that add the finishing touches. Each one has its own method:

* **Author:** `setAuthor` shows a small line at the top, with a name, an icon and, if you want, a link.
* **Thumbnail:** `setThumbnail` puts a small image in the top right corner. It is the ideal spot for an avatar or a logo.
* **Image:** `setImage` displays a large image, at the width of the embed, below the content.
* **Footer:** `setFooter` takes a text and a small icon.
* **Timestamp:** `setTimestamp` shows date and time next to the footer. With no argument it uses the current moment, and Discord converts it to the reader's time zone.

`const embed = new EmbedBuilder() .setColor(0xff7a00) .setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL(), }) .setTitle('Weekly update') .setURL('https://example.com/news') .setDescription('Summary of the changes that landed on the server.') .setThumbnail(interaction.guild.iconURL()) .setImage('https://example.com/banner.png') .setFooter({ text: 'Server staff', iconURL: interaction.client.user.displayAvatarURL(), }) .setTimestamp();`

The `setURL` turns the title into a clickable link. Image URLs need to be public addresses using http or https. If Discord can't download the image, it simply doesn't show up. To use a file from the bot's disk, send it as an attachment and reference it with `attachment://`:

`const { AttachmentBuilder } = require('discord.js'); const file = new AttachmentBuilder('./assets/banner.png'); const embed = new EmbedBuilder() .setTitle('Local banner') .setImage('attachment://banner.png'); await interaction.reply({ embeds: [embed], files: [file] });`

Use `interaction.guild.iconURL()` knowing that, if the server has no icon, it returns `null`. `setThumbnail` accepts that value and the embed goes out with no thumbnail. If you always want an image, pass a fallback: `interaction.guild.iconURL() ?? 'https://example.com/logo.png'`.

## Fields and inline columns[](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#campos)

Fields are name and value pairs, great for short, repetitive data. With `inline: true` Discord places up to three fields side by side, like columns. Without that option each field takes a whole line.

`const guild = interaction.guild; const embed = new EmbedBuilder() .setColor(Colors.Green) .setTitle('Server information') .addFields( { name: 'Members', value: String(guild.memberCount), inline: true }, { name: 'Channels', value: String(guild.channels.cache.size), inline: true }, { name: 'Roles', value: String(guild.roles.cache.size), inline: true }, { name: 'Created on', value: '<t:' + Math.floor(guild.createdTimestamp / 1000) + ':D>', }, );`

A field value has to be a string, which is why `String()` wraps the numbers. An empty name or value causes an error. If you need a deliberate blank space, use an invisible character like `'\u200b'`. The snippet `<t:123456:D>` is a Discord timestamp: it shows the date formatted in each reader's time zone and language. The suffix `:R`shows relative time, like "3 days ago".

To build fields from a list (ranking, products, rules), generate the array with `map` and pass it with spread to `addFields(...list)`. Remember the ceiling is 25 fields per embed. Above that, split into pages or into several embeds.

## Official size limits[](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#limites)

Discord rejects the whole message if any of these limits is exceeded, and the error only shows up at runtime, when the API answers. It is worth knowing the table before building embeds with text that comes from users or from a database.

| Embed part                                    | Limit           |
| --------------------------------------------- | --------------- |
| Title                                         | 256 characters  |
| Description                                   | 4096 characters |
| Fields per embed                              | 25              |
| Field name                                    | 256 characters  |
| Field value                                   | 1024 characters |
| Footer text                                   | 2048 characters |
| Author name                                   | 256 characters  |
| Sum of all texts (every embed in the message) | 6000 characters |
| Embeds per message                            | 10              |

The 6000 sum counts title, description, field names and values, footer text and author name, adding up every embed in the same message. It is the limit that catches people by surprise: a 4000 character description with two large fields already goes over.

When the content comes from outside, cut it before sending. A small function solves it:

`function truncate(text, max) { if (text.length <= max) return text; return text.slice(0, max - 3) + '...'; } const embed = new EmbedBuilder() .setTitle(truncate(title, 256)) .setDescription(truncate(description, 4000));`

Cutting the description at 4000, not at 4096, leaves some room for fields and footer inside the 6000 sum. To check the total before sending, add up the length of the texts in the embed JSON:

`function totalLength(embed) { const d = embed.toJSON(); let total = (d.title?.length ?? 0) + (d.description?.length ?? 0) + (d.footer?.text.length ?? 0) + (d.author?.name.length ?? 0); for (const field of d.fields ?? []) { total += field.name.length + field.value.length; } return total; }`

If the text is naturally long, like a list of rules, it is better to split it into several embeds in the same message (up to 10) or into pages with buttons, which you can learn in [buttons and interactive menus](https://streethosting.com.br/en/guides/discord-bots/discord-bot-buttons-select-menus).

## Markdown inside the embed[](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#markdown)

Inside the embed Discord understands most of the markdown from regular messages, but not every place accepts everything. The practical rule:

* **Description and field value:** accept bold, italics, underline, strikethrough, inline code, code blocks, quotes and links with text, in the format `[text](https://...)`. Discord mentions and timestamps are also rendered.
* **Footer and author name:** show plain text only. An asterisk there shows up as an asterisk.
* **Title and field name:**links with text don't work. To link the title use `setURL`.
* **Mentions:** they show up formatted, but notify no one. To actually alert someone, send the mention in `content`.

`` const embed = new EmbedBuilder() .setTitle('Quick rules') .setDescription( '**Respect** everyone.\n' + '~~Spam~~ is not allowed.\n' + '> Questions? Talk to <@&STAFF_ROLE_ID>.\n\n' + 'Also read the [full guide](https://example.com/rules).' ) .addFields({ name: 'Useful command', value: 'Use `/help` to see everything.', }); await interaction.reply({ content: '<@' + interaction.user.id + '>', embeds: [embed], }); ``

In a regular JavaScript string, the `\n` becomes a line break. The backticks of inline code go in normally, as long as the string uses single or double quotes. Only in a template literal would they need a backslash.

## Edit an existing embed[](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#editar-embed)

Status panels, polls and tickets change after they are sent. The embeds that arrive in `message.embeds` are read only, so the way is to create a builder from them, change it and send it again:

`// In a channel, fetching the message by its ID const channel = await client.channels.fetch('CHANNEL_ID'); const message = await channel.messages.fetch('MESSAGE_ID'); const newEmbed = EmbedBuilder.from(message.embeds[0]) .setColor(Colors.Red) .setTitle('Server under maintenance') .setTimestamp(); await message.edit({ embeds: [newEmbed] });`

`EmbedBuilder.from` keeps everything you didn't touch, so you can change only the color or only one field. To change a specific field use `spliceFields(index, 1, newField)`. Inside a reply to a button or menu, the shortcut is `interaction.update({ embeds: [newEmbed] })`, which edits the message where the click happened, with nothing to fetch.

You can only edit messages sent by the bot itself. If the edit will happen often (a panel that updates every minute, for example), space the calls out: editing the same message too much runs into the API rate limit.

## Where to keep the bot running[](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#onde-rodar)

Embeds are light, so a bot that only sends formatted messages fits any plan. On [StreetHosting bot hosting](https://streethosting.com.br/en/bots) you run the Node.js project in a container with a panel, and the Discord Essential plan, at R$ 9.99 per month, brings 1 GB of RAM, 0.5 vCPU, 2 GB of NVMe and up to 2 bots. To understand how to keep the process up all the time, read [Discord bot hosting 24/7](https://streethosting.com.br/en/guides/discord-bots/discord-bot-hosting-24-7).

In this guide

* [Your first embed](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#primeiro-embed)
* [Author, thumbnail, image, footer and timestamp](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#partes-do-embed)
* [Fields and inline columns](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#campos)
* [Official size limits](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#limites)
* [Markdown inside the embed](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#markdown)
* [Edit an existing embed](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#editar-embed)
* [Where to keep the bot running](https://streethosting.com.br/en/guides/discord-bots/discordjs-embed-formatted-messages#onde-rodar)

## Frequently asked questions

What is the character limit of a Discord embed?

The title takes 256 characters, the description 4096, a field name 256, a field value 1024, the footer text 2048 and the author name 256. An embed can have up to 25 fields. The sum of all those texts, across every embed in the same message, cannot go over 6000 characters, and each message takes at most 10 embeds.

Why doesn't my embed show up in the channel?

The most common causes are the bot missing the Embed Links permission in the channel, a completely empty embed, or a value above the limits, which makes the API refuse the message. Read the error in the console: Discord tells you which field went over the limit.

Do mentions inside the embed notify the person?

No. The mention shows up formatted, but only the text part of the message (the content field) triggers the notification. If you need to alert someone, send the mention in content and the rest in the embed.

How do I edit an embed the bot already sent?

Get the message, create a builder from the old embed with EmbedBuilder.from(message.embeds\[0\]), change what you want and call message.edit({ embeds: \[newEmbed\] }). In button replies, interaction.update does the same on the message that was clicked.

Can I use an image from my computer inside the embed?

Yes. Send the file as an attachment with AttachmentBuilder and point the embed to it using the attachment://file-name.png address. It also works with any public image URL, with no attachment.

Next step

See bot plans

24/7 Discord bots with fast activation and support in Portuguese.

[See bot plans](https://streethosting.com.br/en/bots)

## Related guides

[Discord bots Beginner How to create a Discord bot with slash commands Slash commands improve the user experience and make your bot easier to use. Here is a clear path to get started. 2 min Read guide](https://streethosting.com.br/en/guides/discord-bots/create-discord-bot-slash-commands) [Discord bots Intermediate Buttons and select menus in a Discord bot with discord.js v14 Learn to build ButtonBuilder and StringSelectMenuBuilder, read the customId in interactionCreate, meet the 3 second deadline, choose between a collector and a global handler and disable used buttons. 7 min Read guide](https://streethosting.com.br/en/guides/discord-bots/discord-bot-buttons-select-menus) [Discord bots Intermediate Discord suggestion system with voting, approval and a thread An organized suggestions channel: the person uses /suggest, the bot posts an embed with vote buttons, opens a thread for the conversation and staff approve or reject with a reason, changing the embed color. 7 min Read guide](https://streethosting.com.br/en/guides/discord-bots/discord-suggestion-system-bot)

[← Back to the Guide Center](https://streethosting.com.br/en/guides)
