Skip to main content
Righteous

Custom embed codes

Write one embed code (tags, variables and buttons) and reuse it in welcome messages, tickets, level-ups and everywhere else Righteous asks for one.

Righteous asks for an "embed code" in a lot of places: server messages, ticket greetings, level-up announcements, sticky messages, trap DMs and your now-playing card. They all speak the same small language. Those guides cover when each message fires; this one covers what you type into it.

The language has no permission gate of its own: each surface carries one, usually Manage Server. ,customembed (,ce), the scratchpad for trying a code out before you save it, needs Manage Messages.

Post a code and see what you get

A code is plain text with {tag: value} chunks in it, and anything that is not a recognised tag stays plain text. So the simplest possible code is a sentence:

Syntax

,customembed <code>

Example

,customembed hey everyone

Add a tag the parser knows and the same command posts an embed. Parts are separated by $p, and their order does not matter. Each tag is found by name, never by position:

Syntax

,customembed <code>

Example

,customembed {title: Announcement}$p{description: Doors open at 8.}$p{color: #fb4868}

Variables go inside a tag, wrapped in their own braces, and fill in when the message is sent:

Syntax

,customembed <code>

Example

,customembed {title: Welcome}$p{description: {user.mention} is member number {guild.membercount} of {guild.name}.}$p{thumbnail: {user.avatar}}

The embed builder is the same parser in a browser with a live preview, and nothing there is saved or sent. It's the cheap place to get a code wrong. So is ,copyembed <message link>: it reads an existing message back out as the code that would rebuild it, which beats starting from a blank line. You can skip hunting for the link: right-click the message, then Apps → Copy Embed Code, for the same result. (Either way, a message built with Components V2 is refused rather than half-converted — its layout can't be turned back into embed code.)

Build the embed out of tags

TagWhat it setsTakes
{title: text}the headingtext
{description: text}the bodytext
{color: value}the stripe down the lefta hex colour, or random
{url: link}makes the title clickablean http(s) link
{thumbnail: link}the small image, top rightan image link
{image: link}the large image, at the bottoman image link
{author: name && icon && link}the small line above the titlename required, the other two optional
{footer: text && icon}the small line at the bottomtext required, icon optional
{field: name && value}a labelled block: add && inline to sit it beside the next oneboth halves required
{timestamp:}a time on the footernothing; the tag alone is the signal
{content: text}text outside the embed, in the message bodytext, up to 2,000 characters
{button: ...}a button under the messagesee below

Tags with several parts split on &&, so an author name, a footer or a button label cannot contain a literal &&. {field:} is the exception: its name is the first part and the whole remainder is the value, && and all, so {field: A && B && C} gives a field called A whose value is B && C. Loose text in no tag does the same job as {content:}, and several {content:} tags accumulate in code order where a second {title:} is ignored. A chunk that looks like a tag but is not one ({banner: x}, a typo) is dropped silently rather than printed, which is why a misspelling gives you nothing at all rather than an error.

Fill in names and numbers with variables

A variable is a token in braces that resolves when the message is sent. Which ones resolve depends on where the code runs: a welcome message knows the member who just joined, a level-up message knows the level:

FamilyFills in
{user}, {user.mention}, {user.name}, {user.avatar}, {user.joined} …anywhere a code renders: the member the message is about
{guild.name}, {guild.membercount}, {guild.icon}, {channel.name} …anywhere a code renders
{color.random}anywhere: a fresh hex colour on every render
{ticket.id}, {ticket.opener}, {ticket.reason}, {ticket.answer1} …only in the ticket greeting: the one message rendered while a ticket exists
{level}, {xp.into}, {rank}, {reward} …only in a level-up message set with ,levels embeds
{track.name}, {album.cover}, {artist.plays}, {spotify.url} …wherever the member it renders for has a Last.fm account linked

Out of its context a variable renders as its own name, not as nothing. The ticket and level tokens are filled in by a pass that runs only on the surface that owns them; anywhere else they reach the ordinary parser, which has never heard of them and does what it does with any unknown token: drops the braces, keeps the text. So {ticket.id} on the ticket panel renders the bare words ticket.id, which is the usual surprise, since the panel exists before any ticket does. A plain misspelling looks identical: {usr} renders as usr. A bare word in your embed is either a typo or a token asking a surface for something it has no idea about.

The tokens that really do resolve to nothing are the music ones, for a member with no linked account or no scrobble to read.

Every token, searchable, is on the variables page; ,customembed variables prints it in Discord.

Add buttons

Syntax

,customembed <code>

Example

,customembed {description: Read these first.}$p{button: Rules && https://example.com/rules}
PartMeansExample
labelthe text on the buttonRules
linkwhere it sends peoplehttps://example.com
emojiunicode, or one of your server's, written <:name:id>🎧
colourlink, blue, green, grey or redgreen
disabledgreys it outdisabled

Parts are matched by kind rather than position, so a label, a link and an emoji can appear in any order. A label or an emoji is required and the rest is optional; you get up to 25 buttons, five to a row, and one the parser cannot build is dropped while the rest of the message sends.

Only link buttons do anything when clicked. The coloured ones are decoration: the bot acknowledges the click and does nothing. Nor can a button carry an arbitrary image; Discord has no such thing, and a custom emote is what that idea becomes.

Delete the message after a while

++delete is not a tag. A tag describes what the message is; an action tells the bot what to do with it, never renders, and goes at the very end of the code:

Syntax

,customembed <code> ++delete <seconds>

Example

,customembed {description: Back in five.} ++delete 30

One flag, three parsers: the surface you write it on decides what it will take.

Where you write itTakesDefault
A code rendered as it is sent: ,customembed, ,webhooks send, a ticket greetinga whole number of seconds, 1 to 600off
A saved server message: ,welcomemessage add, and its goodbye, boost and unboost twinsa whole number of seconds, 1 to 60off
A saved level-up message: ,levels embedsa duration with a unit, up to 1h (30s, 5m) or off to clear itoff

The differences bite. ++delete 30 is thirty seconds on the first two rows and an error on the third, which wants 30s. ++delete 1h is an hour on the third row and refused outright on the first, which answers "Auto-delete must be a whole number, like 3."

Rows one and three refuse a value they cannot read rather than rounding it to something convenient: deleting somebody's message at a time they never asked for is worse than not deleting it. The saved server messages coerce instead, and that makes the second row the one to be careful on: it checks your text against the 1-to-60 bounds before turning it into a number, so anything with a letter in it satisfies neither bound and slips through, then gets truncated to whatever digits it happens to start with. ++delete 1h saves as 1 second. ++delete 6O, typed with a letter O, saves as 6 seconds. Neither says a word.

Where the flag may sit differs too, and that one costs you text. ,customembed and ,levels embeds look at the very end of the code and nowhere else, so a ++delete in the middle is left alone: ,customembed renders it as ordinary text, and ,levels embeds refuses the save and asks you to put the flag at the end with a value. The four server-message commands look for the word anywhere and cut the code off where they find it, so a mid-code ++delete silently truncates everything after it: ,welcomemessage add #general {description: Hi} ++delete 5 $p{footer: bye} saves a welcome message with no footer and tells you it worked. Put the flag last, always.

Four surfaces ignore the timer. Join DMs and trap DMs do, because a DM the member may not have opened should not vanish with no trace; ,pages and ,editembed do because they edit a message that already exists rather than sending one, and a timer would kill a paginator mid-read.

The flag is still stripped there, so it never leaks into the message as visible text. On welcome, goodbye, boost, unboost and level-up messages it is read when you save the code and kept as that message's own auto-delete setting. See server messages.

Switch the whole message to Components V2

Adding {v2:} anywhere renders the code as a Components V2 container instead of a classic embed. Same $p syntax, same variables, same buttons, plus three tags with no classic equivalent:

Syntax

,customembed <code>

Example

,customembed {v2:}$p{title: New here?}$p{gallery: https://example.com/a.png && https://example.com/b.png}$p{separator:}$p{section: Start in the rules channel && https://example.com/icon.png}
TagWhat it adds
{gallery: link && link …}up to ten images in one grid; an {image:} counts as the first of them
{section: text && accessory}text beside a thumbnail (one image link) or a button, never both
{separator:}a divider line

The syntax reads more general than it is, so know three limits first: only the first {section:} renders, {separator:} renders once however many you write, and the layout order is fixed (text, gallery, section, separator, buttons) wherever the tags sit. It is a template, not a layout language.

Components V2 also cannot show an author or footer icon or put fields into inline columns; those are dropped with a warning rather than refusing the save. Its text budget is 4,000 characters where a classic embed's is 6,000, so a code that fits as an embed can be too big once {v2:} is added. ,webhooks send refuses a {v2:} code outright and says why: a guild webhook can only send classic embeds and buttons, so refusing beats posting half a message.

,pages is the one to watch, because it refuses much less than you would expect. The only code it turns down is one that would leave a page set with Components V2 on some pages and classic embeds on others: Discord fixes a message as one or the other for life, so a set that mixed them could not page through itself. A set is built from a message that already carries a classic embed, which means a {v2:} page saves cleanly into a single-page set, reports success, and then never renders: the paginator finds a container where its message is a classic embed and declines the edit rather than throw, so the message keeps showing what it showed before. Keep page sets classic and use ,customembed where you want a container.

Know where a code works

WhereSet with
Welcome, goodbye and boost messages, and join DMsServer messages
Punishment confirmations (Server Premium),custommessage
Ticket greetings, and the ticket panel,ticket message, ,ticket setup
Level-up announcements,levels embeds
Sticky messages (Server Premium),stickymessage add
Trap channel DMs,trap dm, Trap channel
A one-off webhook message,webhooks send
Your now-playing card (Self Premium),lastfm mode
A one-off post, or editing one you already sent,customembed, ,editembed

Autoresponders do not take embed codes

,autoresponder replies go through the variable pass only. They never reach the embed parser. {user} and {guild.name} fill in as normal, but {title: Hi} posts as the literal text title: Hi, $p prints as itself, and there is no embed and no button. Music tokens do not resolve there either. Keep autoresponder replies to plain text and variables: ++delete is the one flag they do take, because the autoresponder parses it itself rather than getting it from the embed parser.

Common issues

Nothing posted at all. A code whose every tag is misspelled parses to an empty message, which Discord will not accept, so nothing is sent, quietly. Add a {description:} and see if that posts.

A tag printed as visible text. You are on a surface that does not parse codes: the autoresponder is the one that catches people. Where codes are parsed an unrecognised tag is dropped, never printed, so title: Hi in a channel means the whole string was treated as text.

A word from my code shows up bare in the embed. A token that lost its braces and kept its text is either a misspelling ({usr} becomes usr) or a token on a surface that cannot fill it in: a {ticket.*} on the ticket panel, a {level} outside a level-up message. Check the spelling on the variables page, then check you are on the surface that owns the token. A variable that came out empty is a different fault and almost always a music one: no linked account, or nothing scrobbled yet.

A line starting with a word and a colon vanished. Notice: read this is shaped like a mistyped tag, so it goes with the rest of them. Wrap it: {content: Notice: read this}. An unreadable colour is the same class of quiet failure: the embed falls back to black rather than not posting at all, so use a hex value like #fb4868, or random.

A {v2:} page saved, but the paginator kept showing the old page. A paginated message is created as a classic embed and Discord will not convert one into a Components V2 container, so the page is stored and then quietly refused at render time. Put that page back to classic tags.

A {v2:} code posted as an ordinary embed. The Components V2 render hit a cap, so the bot fell back to a classic embed rather than posting nothing, and the gallery, section and separator went with it. Where you save a code, the bot refuses instead and names the cap it busted.

Nothing here fixed it. The shared common issues page works the same ground from the symptom end.