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

server-info.js (event: messageCreate)
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

MethodNotes
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- second

Enums you will actually use

EnumExamplesUsed for
ColorsColors.Red, Colors.Blurple, Colors.GoldsetColor
ButtonStylePrimary, Secondary, Success, Danger, LinkButtons (Lesson 7)
TextInputStyleShort, ParagraphModal inputs (Lesson 8)
ChannelTypeGuildText (0), GuildVoice (2), GuildCategory (4)Channel checks and creation
PermissionFlagsBitsManageMessages, BanMembersRole permission values
ThreadAutoArchiveDurationOneHour, OneDay, OneWeekThread 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: count throws for numbers; wrap with String(...).
  • Empty fields are invalid. Discord rejects embeds with empty name or value; use a placeholder like "-".
  • Images need public URLs. setImage/setThumbnail with 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.