Scripting Guide
Lesson 6: Embeds and the discord Module
Importing from the built-in discord module, building embeds, using formatters, and the enums you will reach for most.
The one allowed import
Agents can import exactly one module: "discord". It is bundled into the sandbox and contains builders (embeds, buttons, menus, modals), text formatters, and enums. Importing anything else (discord.js, npm packages, node builtins) fails, because nothing else exists inside the sandbox.
import { EmbedBuilder, Colors, time, userMention } from "discord";Building an embed
import { EmbedBuilder, time } from "discord";
export async function onMessage(message) {
if (message.author.bot) return;
if (message.content !== "!serverinfo") return;
const g = message.guild;
const embed = new EmbedBuilder()
.setTitle(g.name)
.setDescription(g.description ?? "No description set.")
.setColor("Blurple") // color name, hex string "#3da2f3", or number
.setThumbnail(g.iconURL)
.addFields(
{ name: "Members", value: String(g.memberCount), inline: true },
{ name: "Boosts", value: String(g.premiumSubscriptionCount), inline: true },
// time() renders a live Discord timestamp from a Date or Unix seconds.
{ name: "Created", value: time(Math.floor(g.createdTimestamp / 1000)) },
)
.setFooter({ text: "Requested by " + message.author.username })
.setTimestamp();
await message.reply({ embeds: [embed] });
}Expected behavior: !serverinfo produces a card with the server name, a colored stripe, three fields (two on one line), the requester in the footer, and a localized creation date that renders in each viewer's timezone.
EmbedBuilder reference
| Method | Notes |
|---|---|
setTitle(text) / setDescription(text) | Description supports Discord markdown; max 4096 chars. |
setColor(value) | Name from Colors (e.g. "Red", "Blurple"), "#rrggbb", or a number. |
setURL(url) | Makes the title a link. |
setAuthor({ name, url, iconURL }) / setFooter({ text, iconURL }) | Small header and footer rows. |
setImage(url) / setThumbnail(url) | Large image below, small image top-right. |
addFields({ name, value, inline? }, ...) / setFields(...) / spliceFields(...) | Up to 25 fields; inline fields share rows. |
setTimestamp(ms?) | Footer timestamp; defaults to now. |
Text formatters
Formatters return plain strings, so they work in any content, embed, or field:
import {
bold, italic, underline, strikethrough, spoiler,
inlineCode, codeBlock, quote, blockQuote, hyperlink, heading, subtext,
userMention, channelMention, roleMention,
time, TimestampStyles, orderedList, unorderedList,
} from "discord";
bold("important") // **important**
inlineCode("npm run dev") // `npm run dev`
codeBlock("js", "let x = 1;") // fenced js code block
userMention("123456789012345678") // <@123456789012345678>
time(1718000000, TimestampStyles.RelativeTime) // "in 3 days" style stamp
hyperlink("the docs", "https://example.com") // masked link
unorderedList(["first", "second"]) // - first\n- secondEnums you will actually use
| Enum | Examples | Used for |
|---|---|---|
Colors | Colors.Red, Colors.Blurple, Colors.Gold | setColor |
ButtonStyle | Primary, Secondary, Success, Danger, Link | Buttons (Lesson 7) |
TextInputStyle | Short, Paragraph | Modal inputs (Lesson 8) |
ChannelType | GuildText (0), GuildVoice (2), GuildCategory (4) | Channel checks and creation |
PermissionFlagsBits | ManageMessages, BanMembers | Role permission values |
ThreadAutoArchiveDuration | OneHour, OneDay, OneWeek | Thread settings |
MessageFlags, ComponentType, ActivityType, ... | Less common; all standard Discord enums are exported |
Multiple embeds and combinations
A single send accepts up to 10 embeds alongside content, components, and files: await channel.send({ content: "Today:", embeds: [a, b], components: [row] }). Builders can be passed directly; you never need to call .toJSON() yourself.
Common pitfalls
- Field values must be strings.
value: countthrows for numbers; wrap withString(...). - Empty fields are invalid. Discord rejects embeds with empty
nameorvalue; use a placeholder like"-". - Images need public URLs.
setImage/setThumbnailwith private or non-http(s) URLs will not render, and file attachments are validated and rejected. - `time()` wants seconds. Timestamps in payloads are milliseconds; divide by 1000 first.
Exercise
Build a !userinfo command that replies with an embed showing the author's avatar (message.author.avatarURL) as the thumbnail, their account creation date and server join date as relative timestamps, and their role count. Bonus: color the embed with the member's displayHexColor.