Cómo enviar embeds con tu bot de Discord (discord.js y discord.py)
Un embed es la tarjeta que mandan los bots: una franja de color, un título, un texto, campos en columnas, una imagen y un pie. Aquí tienes cómo enviarlo en discord.js y en discord.py con un bot completo, cómo poner imágenes de tu ordenador, cómo editarlo después y los límites que hacen que Discord lo rechace.
Qué lleva un embed
Todas sus partes son opcionales, pero un embed tiene que llevar algo: el color de la franja de la izquierda; el autor (un nombre arriba, con icono y enlace); el título, que puede ser un enlace; la descripción, el texto principal; hasta 25 campos, cada uno con su nombre y su valor, que pueden ir en columnas («en línea»); una miniatura arriba a la derecha; una imagen grande abajo; y el pie, con icono y la hora.
Un mensaje puede llevar texto normal y hasta 10 embeds a la vez. El texto normal va fuera del embed, encima, y es donde tienen que ir las menciones: una mención dentro del embed se ve, pero no le llega el aviso a nadie.
Para verlo antes de programarlo, el generador de embeds lo pinta como en Discord y te da el código de discord.js, de discord.py o el JSON de un webhook.
¿Prefieres no montarlo tú? Hosting de bots de Discord desde 1,50 € al mes, sin permanencia y con devolución íntegra en 72 horas. O crea un bot de Discord gratis, sin tarjeta.
En discord.js
Con EmbedBuilder: cada parte es un método (setTitle, setDescription, setColor, addFields, setThumbnail, setImage, setFooter, setTimestamp…) y el embed se manda en la lista embeds, en channel.send o en interaction.reply. El color admite "#5b4ae8", un número como 0x5b4ae8 o un nombre de discord.js como "Blurple".
Este bot publica /info, que contesta con la ficha del servidor: icono y nombre, miembros, fecha de creación (con el formato <t:…:D>, que cada uno ve en su idioma y su zona horaria) y la hora del envío. No necesita ningún intent privilegiado. En discord.js un campo solo va en línea si le pones inline: true.
require("dotenv").config(); // lee el token del fichero .env
const { Client, EmbedBuilder, Events, GatewayIntentBits, InteractionContextType, SlashCommandBuilder } = require("discord.js");
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const info = new SlashCommandBuilder()
.setName("info")
.setDescription("Enseña la ficha del servidor")
.setContexts(InteractionContextType.Guild);
function tarjeta(guild) {
return new EmbedBuilder()
.setColor("#5b4ae8") // el color de la franja de la izquierda
.setAuthor({ name: guild.name, iconURL: guild.iconURL() ?? undefined })
.setTitle("Bienvenido al servidor")
.setURL("https://example.com") // el título se vuelve un enlace
.setDescription("Lee las **normas** antes de escribir.")
.addFields(
{ name: "Miembros", value: String(guild.memberCount), inline: true },
{ name: "Creado", value: `<t:${Math.floor(guild.createdTimestamp / 1000)}:D>`, inline: true },
)
.setFooter({ text: "Escribe /info cuando quieras" })
.setTimestamp(); // la hora, junto al pie
}
client.on(Events.InteractionCreate, async (i) => {
if (!i.isChatInputCommand() || i.commandName !== "info") return;
await i.reply({ embeds: [tarjeta(i.guild)] });
});
client.once(Events.ClientReady, async (c) => {
await c.application.commands.set([info.toJSON()]); // publica /info
console.log(`Conectado como ${c.user.tag}`);
});
client.login(process.env.DISCORD_TOKEN);En discord.py
Con discord.Embed(title=…, description=…, color=…, url=…, timestamp=…) y después add_field, set_author, set_thumbnail, set_image y set_footer. Se manda con embed= (uno) o embeds=[…] (varios), en channel.send o en interaction.response.send_message. El color es un número (0x5B4AE8) o un discord.Color.
Dos cosas que pillan: en discord.py los campos van en línea si no pones inline=False (al revés que en discord.js), y la hora tiene que llevar zona horaria: discord.utils.utcnow(), no datetime.now() a secas. El mismo bot de /info:
import os
import discord
from discord import app_commands
from dotenv import load_dotenv
load_dotenv() # lee el token del fichero .env
client = discord.Client(intents=discord.Intents.default())
tree = app_commands.CommandTree(client)
def tarjeta(guild: discord.Guild) -> discord.Embed:
embed = discord.Embed(
title="Bienvenido al servidor",
url="https://example.com", # el título se vuelve un enlace
description="Lee las **normas** antes de escribir.",
color=0x5B4AE8, # el color de la franja de la izquierda
timestamp=discord.utils.utcnow(), # la hora, junto al pie
)
embed.set_author(name=guild.name, icon_url=guild.icon.url if guild.icon else None)
# en discord.py los campos van en línea si no dices inline=False
embed.add_field(name="Miembros", value=str(guild.member_count))
embed.add_field(name="Creado", value=discord.utils.format_dt(guild.created_at, "D"))
embed.set_footer(text="Escribe /info cuando quieras")
return embed
@tree.command(name="info", description="Enseña la ficha del servidor")
@app_commands.guild_only()
async def info(interaction: discord.Interaction):
await interaction.response.send_message(embed=tarjeta(interaction.guild))
@client.event
async def on_ready():
await tree.sync() # publica /info
print(f"Conectado como {client.user}")
client.run(os.environ["DISCORD_TOKEN"])Imágenes de tu ordenador: attachment://
La imagen, la miniatura y los iconos del autor y del pie solo admiten una dirección https:// o un fichero que mandas con el mismo mensaje. Para usar uno tuyo, adjúntalo y en la imagen pon attachment:// y su nombre. Según la documentación de Discord, así solo valen .jpg, .jpeg, .png, .webp y .gif.
Si la imagen no sale, casi siempre es que el nombre de detrás de attachment:// no es exactamente el del fichero adjunto (mayúsculas incluidas), o que la dirección no es una imagen directa sino una página.
const { AttachmentBuilder, EmbedBuilder } = require("discord.js");
// el nombre del fichero, sin espacios ni tildes: es el que va detrás de attachment://
const archivo = new AttachmentBuilder("./mapa.png", { name: "mapa.png" });
const embed = new EmbedBuilder()
.setTitle("El mapa de hoy")
.setImage("attachment://mapa.png"); // la imagen sale del fichero adjunto
await i.reply({ embeds: [embed], files: [archivo] });# el nombre del fichero, sin espacios ni tildes: es el que va detrás de attachment://
archivo = discord.File("mapa.png", filename="mapa.png")
embed = discord.Embed(title="El mapa de hoy")
embed.set_image(url="attachment://mapa.png") # la imagen sale del fichero adjunto
await interaction.response.send_message(embed=embed, file=archivo)Editar un embed ya enviado
Un bot puede editar sus propios mensajes, embeds incluidos: sirve para un marcador, un estado que cambia o marcar un ticket como resuelto. Se manda el embed entero otra vez; lo más cómodo es partir del que ya tiene el mensaje.
// copia el embed que ya tiene el mensaje y cambia solo lo que quieras
const nuevo = EmbedBuilder.from(mensaje.embeds[0]).setColor("#2ecc71").setFooter({ text: "Resuelto" });
await mensaje.edit({ embeds: [nuevo] });# copia el embed que ya tiene el mensaje y cambia solo lo que quieras
embed = mensaje.embeds[0]
embed.color = 0x2ECC71
embed.set_footer(text="Resuelto")
await mensaje.edit(embed=embed)Los límites, y el error «Invalid Form Body»
Discord rechaza el mensaje entero si un embed se pasa de uno de estos límites (los espacios del principio y del final no cuentan): título, 256 caracteres; descripción, 4096; hasta 25 campos; nombre de un campo, 256; valor de un campo, 1024; pie, 2048; nombre del autor, 256. Y entre todos los embeds del mensaje, título, descripción, campos, pie y autor no pueden pasar de 6000 caracteres.
Al pasarse, la API contesta con el error 50035 («Invalid Form Body») y dice qué parte falla: por ejemplo embeds[0].fields[0].value en discord.js o In embeds.0.fields.0.value en discord.py. El mismo error sale con un campo con el nombre o el valor vacío (si lo calculas, pon algo como «—» cuando no haya nada) o con una dirección que no empieza por http:// o https://.
Si el texto es largo de verdad (una lista de 100 jugadores, unas normas enteras), pártelo: varios campos, varios embeds en el mismo mensaje o varias páginas con botones.
Cuando sale distinto de lo que esperabas
El mensaje sale sin el embed: al bot le falta el permiso «Insertar enlaces» en ese canal. Dáselo a su rol o en «Editar canal» → «Permisos», o saca un enlace de invitación nuevo con la calculadora de permisos de bot.
El error 50006 («Cannot send an empty message»): el mensaje no lleva ni texto ni un embed con algo dentro. Suele ser un embed que se monta vacío por un fallo antes, o en discord.js pasar el embed fuera de la lista (embeds: embed en vez de embeds: [embed]).
La negrita, la cursiva y los enlaces con texto ([texto](https://…)) funcionan en la descripción y en el valor de los campos. El título no los admite: para que sea un enlace, se le pone la dirección con setURL o url=.
Si usas los componentes nuevos de Discord (Components V2, la marca IS_COMPONENTS_V2), ese mensaje ya no puede llevar embeds: el contenido va en los propios componentes.
Tenerlo encendido siempre
Un embed solo lo manda un bot encendido: si lo tienes en tu ordenador, se apaga contigo. Opciones gratis y de pago, comparadas, en dónde alojar tu bot de Discord.
En NapNodes hay un bot gratis de 256 MB que no se apaga por estar vacío; se renueva cada 7 días con «Encender» (sus pegas, en esta guía). Créalo, elige Node.js o Python en «Configuración» → «Lenguaje» y sube tus ficheros en «Archivos» (o pon tu repositorio en «Configuración» → «Repositorio de GitHub»); lo que escriba el bot lo ves en «Consola».
Preguntas frecuentes
¿Cómo se hace un embed en discord.js?
Con new EmbedBuilder() y sus métodos (setTitle, setDescription, setColor, addFields…), y se manda con channel.send({ embeds: [embed] }) o interaction.reply({ embeds: [embed] }).
¿Cómo se hace un embed en discord.py?
Con discord.Embed(title=…, description=…, color=…), add_field y set_footer, y se manda con await channel.send(embed=embed) o interaction.response.send_message(embed=embed).
¿Por qué el embed de mi bot no se ve?
Casi siempre le falta el permiso «Insertar enlaces» en ese canal. Si lo que no sale es la imagen, revisa que la dirección sea https:// y una imagen directa, o que el nombre detrás de attachment:// sea el del fichero adjunto.
¿Cuántos caracteres caben en un embed?
256 en el título, 4096 en la descripción, 25 campos de 256 (nombre) y 1024 (valor), 2048 en el pie, y entre todos los embeds de un mensaje, 6000 en total.
¿Se puede mencionar a alguien dentro de un embed?
La mención se ve, pero no le llega el aviso. Para que le llegue, pon la mención en el texto normal del mensaje, fuera del embed.
¿Te ha servido esta guía?
¿Lo montamos?
Hosting de bots de Discord desde 1,50 € al mes, sin sobreventa: tu RAM es solo tuya. Y si quieres empezar sin pagar, tienes un bot de Discord gratis, sin tarjeta.
Herramienta gratis: Generador de embeds de Discord. Arma un embed con título, color, campos e imágenes, míralo como en Discord y sal con el código de discord.js, discord.py o el JSON de un webhook.
Sigue leyendo
- Botones y menús desplegables en tu bot de Discord (discord.js y discord.py)
Pon botones y menús desplegables en tu bot de Discord con discord.js y discord.py: páginas, responder a tiempo, que sigan tras reiniciar y sus límites.
- Comandos con barra en tu bot de Discord: opciones, subcomandos y permisos
Comandos con barra (slash commands) en discord.js y discord.py: opciones, subcomandos, permisos, respuestas que tardan, autocompletado y por qué no salen.
- Bot de registros para Discord: mensajes borrados y editados, entradas y salidas
Haz un bot de registros para Discord en Python o JavaScript: apunta los mensajes borrados y editados y quién entra y sale, en un canal para moderadores.