Tu colles un lien vers ton site dans un salon Discord, et une petite carte apparaît toute seule : titre, description, une image. Tu n’as rien codé de spécial pour ça, c’est le comportement par défaut.
Discord vient d’ajouter une option en plus : remplacer complètement cette carte par un aperçu construit sur mesure, avec du texte formaté, une galerie d’images, des boutons.
J’ai testé ça sur ce blog, et voici comment ça marche.
Ce qu’il faut avoir en place avant de commencer :
- Un site servi en HTTPS
- Les balises Open Graph déjà présentes (le repli obligatoire, même avec un component embed)
- Un rendu côté serveur (le HTML doit contenir les balises dès la réponse, pas après un script JS)
- Le crawler
Discordbotautorisé si un WAF, un CDN ou un rate-limiter est devant le site - Le payload JSON du component embed prêt (on y vient juste après)
Le comportement par défaut : les balises Open Graph
Sans rien ajouter, voici ce qui se passe : Discord fait une requête sur ton URL, lit le HTML renvoyé par le serveur, et cherche des balises Open Graph dans le <head>.
Si elles sont là, il en sort une carte automatiquement.
<meta property="og:title" content="Titre de la page" />
<meta property="og:description" content="Description de la page" />
<meta property="og:image" content="https://exemple.com/og-image.png" />
<meta property="og:url" content="https://exemple.com/page" />
<meta name="twitter:card" content="summary_large_image" />
Le rendu sur Discord, à partir de ces seules balises :

Retenir trois points sur ce fonctionnement, parce qu’ils comptent pour la suite aussi :
- Les balises doivent être dans le HTML envoyé par le serveur. Discord n’exécute aucun JavaScript, une balise ajoutée après coup par du JS côté client n’existe pas pour lui.
- Le crawler s’identifie avec le user-agent
Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com). Si un WAF ou un rate-limiter bloque les bots, il faut explicitement laisser passerDiscordbot. - La preview est mise en cache environ 30 minutes. Pour forcer un rafraîchissement en test, ajouter un
?v=2dans l’URL suffit.
Je détaille ces balises Open Graph (tailles d’image, balises Twitter Card…) dans Quelques tips pour améliorer son SEO, si tu veux creuser ce côté-là.
Le nouveau format : les component embeds
Si aucun component embed n’est présent sur la page, Discord n’affiche rien de plus que la carte du dessus.
C’est un ajout, pas un remplacement obligatoire : les balises og:* restent nécessaires dans tous les cas. Elles servent de repli si l’embed ne peut pas s’afficher (contexte qui ne le supporte pas, payload invalide, etc).
Le fonctionnement, côté crawler, est le même que pour la carte classique, avec une étape en plus :
- Un lien vers ta page est posté dans un message Discord.
- Discord fetch l’URL et lit le HTML.
- Il cherche en plus une balise de component embed. Si elle existe, il parse le JSON et va chercher les métadonnées de chaque image du payload.
- Si ce payload est valide, il remplace la carte Open Graph par ce rendu custom. Sinon, retour à la case précédente.
Deux façons d’exposer ce JSON :
Option 1, inline, un <script> dans le <head> :
<script id="discord:component-embed" type="application/json">
{
"component": {
"type": 17,
"components": [
{
"type": 10,
"content": "# Patch Notes\n- Correction d'un bug d'affichage"
}
]
}
}
</script>
Option 2, en JSON externe, avec un lien qui pointe vers un fichier sur le même domaine (ou un sous-domaine) :
<link
rel="discord:component-embed"
type="application/json"
href="https://exemple.com/embeds/article.json"
/>
Cette option a une limite stricte : 3000 octets sur le fichier brut. Pour un embed avec galerie d’images et boutons, l’inline passe plus large.
Le component racine doit être un Container (type: 17).
Il peut contenir jusqu’à 40 composants au total, parmi ceux du tableau ci-dessous. N’importe quel autre type de composant invalide tout le payload :
| Type | Composant | Notes |
|---|---|---|
| 1 | Action Row | ligne de boutons |
| 2 | Button | uniquement style lien (style: 5), avec label et/ou emoji |
| 9 | Section | texte + un accessoire (thumbnail ou bouton) |
| 10 | Text Display | markdown Discord (titres, gras, listes, liens…) |
| 11 | Thumbnail | petite image accolée à une Section |
| 12 | Media Gallery | jusqu’à plusieurs images/vidéos, en grand format |
| 14 | Separator | trait de séparation |
| 17 | Container | le composant racine |
Pour les images (Thumbnail et Media Gallery), on ne fournit que l’URL, Discord se charge de récupérer les dimensions et le type de fichier :
{ "url": "https://exemple.com/images/hero.webp" }
Formats acceptés : PNG, GIF, JPEG, WebP, AVIF, et pour la Media Gallery, MP4/MOV/WebM en plus.
Ce que j’ai mis en place sur ce blog
Sur chaque article de ce blog, en plus des balises Open Graph classiques (toujours là en repli), j’envoie maintenant un component embed.
Il est construit à partir des mêmes données que la carte standard : le titre, la description, l’image de couverture, et la couleur d’accent de l’article.
La structure ressemble à ça, simplifiée :
{
"component": {
"type": 17,
"accent_color": 5793266,
"components": [
{
"type": 10,
"content": "# [Titre de l'article](https://thomasbnt.dev/blog/...)\nSa description."
},
{
"type": 12,
"items": [{ "media": { "url": "https://thomasbnt.dev/.../index.png" } }]
},
{
"type": 1,
"components": [
{
"type": 2,
"style": 5,
"url": "https://thomasbnt.dev/blog/...",
"label": "Lire l'article"
},
{
"type": 2,
"style": 5,
"url": "https://thomasbnt.dev/blog/",
"label": "Blog"
}
]
}
]
}
}
Le rendu final sur Discord :

Quelques choix faits en le construisant :
- Media Gallery plutôt que Thumbnail : au départ j’étais parti sur une Section + Thumbnail, mais l’image ressort minuscule à côté du texte. La Media Gallery affiche l’image en grand, comme une vraie bannière.
- Pas de Separator : ça cassait visuellement l’embed pour rien entre le texte et l’image.
- Deux boutons : un pour lire l’article, un pour revenir au blog. Simple, mais ça donne un point d’entrée en plus directement depuis Discord.
accent_color: c’est un entier, pas une couleur hexadécimale. Ce blog a déjà une couleur associée à chaque article (backgroundColordans le frontmatter). Je la convertis simplement en décimal (parseInt(hex.replace("#", ""), 16)) plutôt que d’en gérer une nouvelle.
Comme pour les balises OG, ce script est généré côté serveur au moment du build (le site est en Astro, tout est statique), donc rien à exécuter côté client.
Pièges à éviter
- Un composant hors liste, ou une clé en trop sur un bouton (
id,custom_id…) invalide tout le payload, pas juste le composant fautif. - Retirer les balises OG en pensant qu’elles ne servent plus. Elles restent indispensables : c’est le repli utilisé partout où l’embed ne s’affiche pas.
- Le cache de 30 minutes. En plein test, un
?v=2sur l’URL évite de se demander pendant dix minutes pourquoi rien ne change. - Le budget de 10 secondes. Tout le fetch, images comprises, doit être terminé dans ce délai. Avec une Media Gallery qui charge plusieurs images, c’est plus vite atteint qu’avec une simple
og:image.
Comment tester
Le Embed Debugger de Discord montre exactement comment le crawler lit une URL : quelles balises il a trouvées, et quel format d’embed il a choisi. C’est le premier réflexe avant de chercher un bug ailleurs.
Conclusion
Pour résumer la logique : sans rien faire, Discord affiche déjà une carte à partir des balises og:*.
Ajouter un component embed est une option en plus, pas un remplacement. Il ne s’active que si le payload JSON est présent et valide, sinon Discord retombe automatiquement sur cette carte par défaut.
Sur ce blog, ça reste léger : même titre, même description, même image que la carte classique, juste présentés différemment avec un accès direct au reste du blog.
Ressources
- Documentation officielle des component embeds
: la spec complète, avec tous les types de composants et leurs contraintes.
- Embed Debugger
: pour voir exactement comment Discord lit une URL donnée.
- Quelques tips pour améliorer son SEO : les balises Open Graph, les Core Web Vitals, et le reste des bases techniques.
- Le serveur Discord de ce blog : pour tester tes liens ou discuter de tout ça.
