Script functions
79 global functions available to scripts, grouped by
category. All but one (editResponse) are available on every trigger; the
dashboard's autocomplete only offers what's valid for the script you're editing.
Functions
Moderation(7)
ban(userId, reason, deleteMessageDays): void
Bans a user from the guild. For a temporary ban, record the unban due time in the KV store and unban from a Scheduled script that sweeps it periodically (see the seeded /ban command and Unban Sweeper for the pattern).
userId(string): Discord snowflake ID of the user to ban.reason(string): Reason for the ban.deleteMessageDays(number): Number of days of messages to delete (0-7).
kick(userId, reason): boolean
Kicks a user from the guild. Returns false (rather than failing the script) if they had already left.
userId(string): Discord snowflake ID of the user to kick.reason(string): Reason for the kick.
boolean: true if the user was kicked, false if they weren't in the guild.timeout(userId, reason, durationMinutes): boolean
Times out a user in the guild. Returns false (rather than failing the script) if they had already left.
userId(string): Discord snowflake ID of the user to time out.reason(string): Reason for the timeout.durationMinutes(number): Duration of the timeout in minutes.
boolean: true if the timeout was applied, false if the user wasn't in the guild.unban(userId, reason): boolean
Unbans a user from the guild. Returns false (rather than failing the script) if they weren't banned.
userId(string): Discord snowflake ID of the user to unban.reason(string): Reason for the unban.
boolean: true if a ban was lifted, false if the user wasn't banned.untimeout(userId, reason): boolean
Clears a member's timeout early. Prefer this over timeout(userId, reason, 0): both end the timeout, but only this clears the expiry, so the member stops reading as timed-out in ctx.member.timeoutUntil and in Discord's UI.
userId(string): Discord snowflake ID of the member.reason(string): Audit log reason.
boolean: False when the user is no longer a member of the server.getBans(limit): BanSummary[]
Returns the server's ban list (userId, username, displayName, reason), newest first. Live REST read, so it doesn't spend the hourly action budget.
limit(number): How many to return (1-1000; 0 defaults to 100).
BanSummary[]: Array of bans, capped at the requested limit.isBanned(userId): boolean
Whether a user is currently banned from the server. Cheaper and clearer than scanning getBans(). Live REST read, so it doesn't spend the hourly action budget.
userId(string): Discord snowflake ID of the user.
boolean: True when the user is banned.Channel(12)
setSlowmode(channelId, seconds): void
Sets the slowmode delay for a channel.
channelId(string): Discord snowflake ID of the channel.seconds(number): Slowmode delay in seconds (0 to disable).
getForumTags(channelId): ForumTag[]
Returns the tags defined on a forum or media channel, meaning the set a post can be tagged with, resolved to id/name/emoji. Pair with ctx.thread.tags (the tags actually applied to a new post) on a ThreadCreate script, or call it from any trigger. Live read (not cached); throws if the channel is not a forum/media channel.
channelId(string): Discord snowflake ID of the forum or media channel.
ForumTag[]: Array of the forum's available tags.createChannel(name, type, parentId?, topic?, kvKey?): void
Creates a channel in the guild. When kvKey is set, the new channel's ID is written to that KV key so the script can read it back with getKV immediately.
name(string): Name for the new channel.type(string): Channel kind: "text", "voice", "category", "announcement", "forum", "media", or "stage".parentId(string, optional): Category ID to nest under (empty for none).topic(string, optional): Channel topic/description (empty for none).kvKey(string, optional): KV key to store the new channel ID under (empty to skip).
deleteChannel(channelId): void
Deletes a channel (or thread) in the guild.
channelId(string): Discord snowflake ID of the channel to delete.
modifyChannel(channelId, name, topic): void
Renames and/or re-topics a channel. Pass an empty string for a field to leave it unchanged.
channelId(string): Discord snowflake ID of the channel.name(string): New name (empty to leave unchanged).topic(string): New topic/description (empty to clear).
setChannelTopic(channelId, topic): void
Sets a channel's topic/description.
channelId(string): Discord snowflake ID of the channel.topic(string): The new topic (empty to clear).
listChannels(): ChannelSummary[]
Lists the guild's cached channels (id + name). Cheap cached read (no REST), like listMembers.
ReturnsChannelSummary[]: Array of the guild's channels.renameChannel(channelId, name): void
Renames a channel or category.
channelId(string): Discord snowflake ID of the channel or category.name(string): The new name.
getChannel(channelId): ChannelInfo | null
Returns one channel's detail (name, type, topic, parent, position, nsfw, slowmode), or null when it doesn't exist. listChannels() gives only id and name. Live REST read, so it doesn't spend the hourly action budget.
channelId(string): Discord snowflake ID of the channel.
ChannelInfo | null: The channel, or null when it no longer exists.setChannelPermission(channelId, targetId, allow, deny, reason?, targetIsRole?): boolean
Sets or clears one permission overwrite on a channel: the primitive behind a lockdown or raid-mode script, since slowmode alone cannot stop posting. Permissions named in neither list are left to inherit. Passing BOTH lists empty removes the overwrite entirely, which is how a lockdown is lifted, and is different from denying nothing. To lock a channel, deny ["SendMessages"] for the @everyone role, whose ID is the server's own ID (ctx.guildId).
channelId(string): Discord snowflake ID of the channel.targetId(string): Role ID (or user ID when targetIsRole is false). Use ctx.guildId for @everyone.allow(string[]): Permission names to explicitly allow, e.g. ["SendMessages"]. Empty for none.deny(string[]): Permission names to explicitly deny. Empty for none.reason(string, optional): Audit log reason.targetIsRole(boolean, optional): True (the default) when targetId is a role; false for a per-member overwrite.
boolean: True once applied.getEmojis(): EmojiSummary[]
Returns the server's custom emoji (id, name, animated, available, mention, imageUrl). The mention field is the "<:name:id>" form you paste into message content. Live REST read, so it doesn't spend the hourly action budget.
ReturnsEmojiSummary[]: Array of the server's custom emoji.deleteEmoji(emojiId, reason?): boolean
Deletes a custom emoji from the server. There is deliberately no createEmoji(): creating one means uploading image bytes, which would mean the bot fetching a script-supplied URL server-side, and that is a request-forgery surface Mallard doesn't open.
emojiId(string): Discord snowflake ID of the emoji.reason(string, optional): Audit log reason.
boolean: False when the emoji was already gone.Threads(7)
setForumTags(threadId, tagIdsJson): void
Replaces the forum tags applied to a forum/media post. tagIdsJson is a JSON array of tag ID strings (e.g. from getForumTags or ctx.thread.tags); an empty array clears all tags.
threadId(string): Discord snowflake ID of the forum post (thread).tagIdsJson(string): JSON array of tag ID strings to apply.
archiveThread(threadId, archived): void
Archives or unarchives a thread / forum post.
threadId(string): Discord snowflake ID of the thread.archived(boolean): true to archive, false to unarchive.
lockThread(threadId, locked): void
Locks or unlocks a thread / forum post (locked threads can't get new messages).
threadId(string): Discord snowflake ID of the thread.locked(boolean): true to lock, false to unlock.
deleteThread(threadId): void
Deletes a thread / forum post.
threadId(string): Discord snowflake ID of the thread to delete.
createThread(channelId, name, isPrivate, userIds, initialMessage, kvKey): void
Creates a thread under a channel, optionally adding initial members and an opening message. The new thread's ID is written to the KV store under kvKey (as a JSON string) before this call returns, so getKV(kvKey) can read it immediately.
channelId(string): Discord snowflake ID of the parent channel.name(string): Thread name.isPrivate(boolean): Create a private thread.userIds(string): Comma-separated user IDs to add to the thread, or empty string.initialMessage(string): Message posted into the thread after creation, or empty string.kvKey(string): KV key that receives the created thread's ID, or empty string.
addThreadMember(threadId, userId): void
Adds a user to a thread.
threadId(string): Discord snowflake ID of the thread.userId(string): Discord snowflake ID of the user.
removeThreadMember(threadId, userId): void
Removes a user from a thread.
threadId(string): Discord snowflake ID of the thread.userId(string): Discord snowflake ID of the user.
Messaging(9)
sendMessage(channelId, content, embeds?, buttons?, pings?, replyToMessageId?): void
Sends a message to a channel. Pass embeds and/or buttons to attach them; both are plain arrays, no JSON.stringify needed.
channelId(string): Discord snowflake ID of the channel.content(string): Message content (empty string for embed-only).embeds(EmbedData[], optional): Up to 10 embeds. A single embed object is also accepted.buttons(ButtonData[], optional): Buttons to attach, up to 25 (5 per row).pings(boolean, optional): Whether @mentions in the content actually notify (ping). Defaults to true; pass false to send a silent mention (still shows as a tag, no notification).replyToMessageId(string, optional): Send this as a reply to a message in the same channel. If that message has since been deleted, the message still posts, just without the reply. To repost a message elsewhere instead, use forwardMessage.
sendEmbed(channelId, title?, description, colorArgb?): void
Sends a single embed to a channel, as a shorthand for sendMessage with one embed object. The description is the body text and is required, since Discord rejects an embed carrying neither a title nor a description; the title is optional. colorArgb is the stripe down the left edge as a decimal number (e.g. 15548997 for red). For several embeds, an author or footer, a timestamp or an image, use sendMessage.
channelId(string): Discord snowflake ID of the channel.title(string, optional): Embed title (empty string for none).description(string): Embed body text.colorArgb(number, optional): Colour as a decimal number (omit for none).
forwardMessage(sourceChannelId, messageId, targetChannelId): void
Forwards an existing message into another channel, the same way the Discord client's Forward does: the original is shown as a snapshot, with no content of your own attached. Both channels must belong to this guild. Discord refuses to forward polls, calls, and system messages, and refuses any message the bot can't read; either ends the script, so guard with getMessage first if the source might be gone. Counts as one rate-limited REST action. To add a comment, send it as a separate sendMessage.
sourceChannelId(string): Discord snowflake ID of the channel holding the message to forward (must be in this guild).messageId(string): Discord snowflake ID of the message to forward.targetChannelId(string): Discord snowflake ID of the channel to forward it into (must be in this guild).
forwardDm(targetChannelId): void
Forwards the DM that triggered this script into one of this guild's channels or threads, exactly as the Discord client's Forward does: the original appears as a snapshot carrying its text, attachments and embeds, so nothing has to be copied or re-linked. DirectMessageCreate only, and it deliberately takes no source arguments: the DM being forwarded is always the one that triggered the run. Like forwardMessage it carries no content of its own (Discord refuses that, error 160011), so send the details embed and any buttons as a separate sendMessage. Counts as one rate-limited REST action. A target thread that has been deleted or archived fails the call, which ends the script unless you catch it.
targetChannelId(string): Discord snowflake ID of the channel or thread to forward the DM into (must be in this guild).
sendDm(userId, content, embeds?, buttons?, pings?): boolean
Sends a direct message to a user. DMs always carry an embed attributing them to this server. Whether a user accepts DMs is their own privacy setting, so a refused DM returns false rather than failing the script. Check the result if you want to say so; the seeded /ban does.
userId(string): Discord snowflake ID of the user.content(string): Message content (empty string for embed-only).embeds(EmbedData[], optional): Up to 10 embeds. A single embed object is also accepted.buttons(ButtonData[], optional): Buttons to attach, up to 25 (5 per row).pings(boolean, optional): Whether @mentions in the content actually notify (ping). Defaults to true; pass false to send a silent mention.
boolean: true if the DM was delivered, false if the user doesn't accept DMs from this server, has blocked the bot, or shares no mutual guild.editResponse(content, embeds?, buttons?, pings?): voidinteraction triggers only
Edits the bot's reply to the interaction. The reply is already showing a "thinking…" state by the time the script runs, so this fills it in. Call it as the last step. Each call fully replaces the message: embeds and buttons you don't pass are removed. Whether only the invoking user sees it is the script's Ephemeral setting.
content(string): Message content (empty string for embed-only).embeds(EmbedData[], optional): Up to 10 embeds. A single embed object is also accepted.buttons(ButtonData[], optional): Buttons to attach, up to 25 (5 per row).pings(boolean, optional): Whether @mentions in the content actually notify (ping). Defaults to true; pass false to send a silent mention.
sendWebhookMessage(channelId, username, avatarUrl, content, embeds?, pings?): void
Sends a message through a channel webhook with a custom username and avatar (persona messages). The webhook is created on first use. Webhook messages cannot carry buttons. channelId may be a thread, in which case the webhook is created on its parent channel and the message is posted into the thread.
channelId(string): Discord snowflake ID of the channel, or a thread.username(string): Display name for the webhook message.avatarUrl(string): Avatar image URL (empty string for the default).content(string): Message content.embeds(EmbedData[], optional): Up to 10 embeds. A single embed object is also accepted.pings(boolean, optional): Whether @mentions in the content actually notify (ping). Defaults to true; pass false to send a silent mention.
sendPoll(channelId, question, answersJson, durationHours, allowMultiselect?): void
Sends a native Discord poll to a channel. answersJson is a JSON array of 1-10 answer strings.
channelId(string): Discord snowflake ID of the channel.question(string): The poll question.answersJson(string): JSON array of answer strings (1-10).durationHours(number): How long the poll runs, in hours (defaults to 24).allowMultiselect(boolean, optional): Whether voters can pick multiple answers. Defaults to false.
endPoll(channelId, messageId): void
Immediately closes a poll the bot posted.
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the poll message.
Messages(13)
getMessage(channelId, messageId): CachedMessage | null
Fetches a single message from Discord live over REST, or null if it no longer exists. The returned message's reactions field is a per-emoji count/me summary (see getReactionUsers for who reacted). Counts as one rate-limited REST action. The channel must belong to this guild.
channelId(string): Discord snowflake ID of the channel (must be in this guild).messageId(string): Discord snowflake ID of the message.
CachedMessage | null: The message, or null when it doesn't exist.getMessages(channelId, max): CachedMessage[]
Fetches a channel's most recent messages from Discord live over REST, newest first (up to 100). Each message's reactions field is a per-emoji count/me summary (see getReactionUsers for who reacted). Counts as one rate-limited REST action. The channel must belong to this guild.
channelId(string): Discord snowflake ID of the channel (must be in this guild).max(number): Maximum messages to return (1-100; capped at 100).
CachedMessage[]: Newest-first array of messages (empty when the channel has none).deleteMessage(channelId, messageId): boolean
Deletes a single message. Returns false (rather than failing the script) if it was already gone.
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the message.
boolean: true if the message was deleted, false if it no longer existed.bulkDelete(channelId, count): void
Deletes the most recent N messages in a channel (1-100). Messages older than 14 days are deleted individually, which is slower.
channelId(string): Discord snowflake ID of the channel.count(number): Number of recent messages to delete (1-100).
addReaction(channelId, messageId, emoji): void
Adds a reaction to a message as the bot. Emoji is a unicode emoji ("🦆") or a custom emoji as "name:id".
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the message.emoji(string): Unicode emoji or custom emoji as "name:id".
removeReaction(channelId, messageId, emoji, userId): void
Removes reactions for an emoji from a message: one user's reaction when userId is set, or every user's reaction for that emoji when userId is an empty string.
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the message.emoji(string): Unicode emoji or custom emoji as "name:id".userId(string): User whose reaction to remove, or empty string for all users.
clearReactions(channelId, messageId): void
Removes every reaction from a message.
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the message.
getReactionUsers(channelId, messageId, emoji, max): string[]
Returns the user IDs who reacted to a message with a specific emoji. Discord's endpoint is per-emoji, so read the emoji list off getMessage()'s reactions summary first, then call this once per emoji you care about. Counts as one rate-limited REST action. The channel must belong to this guild.
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the message.emoji(string): Unicode emoji or custom emoji as "name:id".max(number): Maximum users to return (1-100; capped at 100).
string[]: User IDs who reacted with that emoji (empty if none).pinMessage(channelId, messageId): void
Pins a message in its channel.
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the message.
unpinMessage(channelId, messageId): void
Unpins a message in its channel.
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the message.
publishMessage(channelId, messageId): void
Publishes (crossposts) a message in an announcement channel.
channelId(string): Discord snowflake ID of the announcement channel.messageId(string): Discord snowflake ID of the message.
editMessage(channelId, messageId, content, embeds?, buttons?, pings?): void
Edits a message the bot previously sent in a channel. The edit fully replaces the message: embeds and buttons you don't pass are removed.
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the message to edit.content(string): New message content (empty string for embed-only).embeds(EmbedData[], optional): Up to 10 embeds. A single embed object is also accepted.buttons(ButtonData[], optional): Buttons to attach, up to 25 (5 per row).pings(boolean, optional): Whether @mentions in the new content notify. Defaults to true.
getPins(channelId): PinnedMessage[]
Returns a channel's pinned messages (id, channelId, authorId, content). Live REST read.
channelId(string): Discord snowflake ID of the channel.
PinnedMessage[]: Array of the channel's pinned messages.Members(13)
hasRole(userId, roleId): boolean
Returns true when the user currently has the given role. Backed by the bot's guild-member cache (kept live by member gateway events and re-paged hourly); a user not in the cache returns false.
userId(string): Discord snowflake ID of the user to check.roleId(string): Discord snowflake ID of the role. An empty string returns false rather than throwing, so an unconfigured role ID (e.g. from ctx.env) can be checked safely.
boolean: True when the user has the role; false if not, or if they aren't a cached member.listMembers(afterUserId, limit): MemberSummary[]
Returns a page of cached guild members ordered by user ID. The member cache is kept live from gateway events and fully re-paged hourly (requires the bot's Server Members privileged intent). Page with the last userId of the previous call.
afterUserId(string): Return members with a user ID greater than this; empty string starts from the beginning.limit(number): Page size (1-500; 0 defaults to 100).
MemberSummary[]: Array of cached members (empty when the cache has not been populated yet).hasPermission(userId, permission): boolean
Whether a member holds a server-level permission, computed the way Discord does: the @everyone role plus every role they hold, with Administrator and server ownership each granting everything. Use this instead of hard-coding moderator role IDs. NOTE: this is server-level and does NOT apply channel permission overwrites, so it answers "may they do this in general", not "may they do this in #channel". Like hasRole(), an uncached member returns false.
userId(string): Discord snowflake ID of the member.permission(string): Permission name, e.g. "ManageMessages", "BanMembers", "KickMembers", "ManageRoles", "ManageChannels", "MentionEveryone", "Administrator".
boolean: True when the member holds the permission.createInvite(channelId, maxAgeSeconds, maxUses, temporary, reason?): InviteSummary
Creates an invite to a channel and returns it.
channelId(string): Channel the invite should point at.maxAgeSeconds(number): Lifetime in seconds (0 = never expires).maxUses(number): Use limit (0 = unlimited).temporary(boolean): Whether joiners get temporary membership (kicked on disconnect unless given a role).reason(string, optional): Audit log reason.
InviteSummary: The created invite, including its code.deleteInvite(code, reason?): boolean
Revokes an invite by its code.
code(string): The invite code (the part after discord.gg/).reason(string, optional): Audit log reason.
boolean: False when the invite was already gone.getInvites(): InviteSummary[]
Returns the server's active invites, including how many times each has been used. This is the only way to read a use count: InviteCreate fires before anyone has joined, and GuildMemberAdd doesn't say which invite was used. To attribute a join, store this list in the KV store and on each join re-read it and find the code whose uses went up. Live REST read, so it doesn't spend the hourly action budget.
ReturnsInviteSummary[]: Array of the server's active invites.getMember(userId): MemberSummary | null
Returns one cached guild member (userId, username, displayName, nickname, avatarUrl, joinedAt, roleIds), or null if not cached. Cheap cached read, no REST. For the user who triggered the script, ctx.actor already holds this, so no call is needed.
userId(string): Discord snowflake ID of the member.
MemberSummary | null: The member, or null when not cached.listRoles(): RoleSummary[]
Lists the guild's cached roles (id + name). Cheap cached read (no REST).
ReturnsRoleSummary[]: Array of the guild's roles.setNickname(userId, nickname): void
Sets or clears a member's nickname (empty string resets it to their username).
userId(string): Discord snowflake ID of the member.nickname(string): The new nickname (empty to reset).
moveMember(userId, channelId): void
Moves a member to a voice channel (they must already be connected to voice).
userId(string): Discord snowflake ID of the member.channelId(string): Voice channel to move them into.
disconnectMember(userId): void
Disconnects a member from voice.
userId(string): Discord snowflake ID of the member.
muteMember(userId, muted): void
Server-mutes or unmutes a member in voice.
userId(string): Discord snowflake ID of the member.muted(boolean): true to mute, false to unmute.
deafenMember(userId, deafened): void
Server-deafens or undeafens a member in voice.
userId(string): Discord snowflake ID of the member.deafened(boolean): true to deafen, false to undeafen.
HTTP(1)
fetch(url, options?): FetchResponse
Performs an HTTP request to an external API or feed and returns {status, headers, body} (body is a string, so parse JSON/XML yourself). Restrictions: https only (http via "fetch_allow_http" in the env record), standard ports only, GET/POST/PUT/PATCH/DELETE/HEAD only, 5s default timeout (up to 15s via options.timeoutMs), 1 MB response cap, 30 requests/minute per guild plus an hourly budget (see ctx.fetchCallsRemaining), and private/internal addresses are blocked. options is an object or JSON string: {method, headers (accept, content-type, authorization, user-agent, client-id, api-key, anthropic-version, and any "x-*" header), body, timeoutMs}. Any "{{secret:NAME}}" placeholder from ctx.secret (admin-only) in the url, a header value, or the body is replaced with the real secret just before the request is sent.
url(string): Absolute https URL to request.options(object | string, optional): Request options ({method, headers, body, timeoutMs}), as an object or JSON string, or omitted for a plain GET.
FetchResponse: Object with status (number), headers (Record<string,string>) and body (string).Roles(6)
addRole(userId, roleId): void
Adds a role to a guild member.
userId(string): Discord snowflake ID of the user.roleId(string): Discord snowflake ID of the role.
removeRole(userId, roleId): void
Removes a role from a guild member.
userId(string): Discord snowflake ID of the user.roleId(string): Discord snowflake ID of the role.
createRole(name, colorArgb?, hoist?, mentionable?, reason?): RoleInfo
Creates a role and returns it. The new role has NO permissions: a script that could mint a permission it doesn't itself hold would be a privilege-escalation path, so permissions stay something a human sets in Discord.
name(string): Role name.colorArgb(number, optional): Colour as a number (e.g. 0xED4245 / 15548997 for red). Omit for no colour.hoist(boolean, optional): Show the role separately in the member list.mentionable(boolean, optional): Let anyone @mention the role.reason(string, optional): Audit log reason.
RoleInfo: The created role.modifyRole(roleId, name?, colorArgb?, hoist?, mentionable?, reason?): boolean
Renames or recolours a role. Omitted parameters are left unchanged. Permissions are not settable here, for the same reason as createRole().
roleId(string): Discord snowflake ID of the role.name(string, optional): New name, or omit to keep it.colorArgb(number, optional): New colour as a number, or omit to keep it.hoist(boolean, optional): Show separately in the member list.mentionable(boolean, optional): Let anyone @mention it.reason(string, optional): Audit log reason.
boolean: False when the role no longer exists.deleteRole(roleId, reason?): boolean
Deletes a role from the server.
roleId(string): Discord snowflake ID of the role.reason(string, optional): Audit log reason.
boolean: False when the role was already gone.setRoles(userId, roleIds, reason?): boolean
Replaces a member's entire role set in one call. Prefer this over a loop of addRole() / removeRole(): those are one action each against the hourly budget and leave the member in visible intermediate states, whereas this is a single action and a single change. Roles Discord manages itself (bot and integration roles, the booster role) cannot be set and must be left in the list as they are.
userId(string): Discord snowflake ID of the member.roleIds(string[]): The complete set of role IDs the member should end up with.reason(string, optional): Audit log reason.
boolean: False when the user is no longer a member of the server.KV Store(4)
getKV(key): string | null
Retrieves a value from the guild's key-value store. The reserved system keys "env" and "secrets" are not accessible here; read them through ctx.env / ctx.secret instead.
key(string): Key to look up.
string | null: The stored JSON string, or null if not found / expired.setKV(key, jsonValue, ttlSeconds): void
Stores a value in the guild's key-value store, overwriting any existing value. Persisted to the database the moment this call returns, so it is visible to getKV right away and kept even if the script errors or times out afterward. The reserved system keys "env" and "secrets" cannot be written (they are managed on the dashboard's Database page).
key(string): Key to store under.jsonValue(string): JSON-serialised value to store.ttlSeconds(number): Time-to-live in seconds; use 0 for no expiry.
deleteKV(key): void
Deletes a key from the guild's key-value store. The reserved system keys "env" and "secrets" cannot be deleted.
key(string): Key to delete.
listKV(prefix, limit): string[]
Lists the server's stored keys, optionally only those starting with a prefix, in alphabetical order. Returns KEYS, not values: a store can run to megabytes, so getKV() the ones you want. This is what makes namespaced keys ("warns:123") usable as a collection. Expired keys and the reserved "env"/"secrets" rows are never listed.
prefix(string): Only keys starting with this. Empty string lists everything.limit(number): How many to return (1-1000; 0 defaults to 100).
string[]: Array of matching keys.Utility(3)
log(message): void
Logs a message. It appears in the run's trace on the Logs page: expand any execution to see your log output interleaved with the Discord, fetch and database calls the script made, in the order they happened. Traces are kept for 8 days and are visible to anyone with dashboard access to this server, so don't log anything you wouldn't want kept.
message(string): Message to log.
formatTimestamp(isoOrEpoch, style?): string
Turns an ISO 8601 timestamp (any of the ctx date fields) or a unix-seconds number into Discord's timestamp markup, which renders in each reader's own timezone and language. No Discord call, so it costs nothing.
isoOrEpoch(string): An ISO 8601 timestamp (e.g. ctx.member.joinedAt) or unix seconds.style(string, optional): One of t (short time), T (long time), d (short date), D (long date), f (short date+time), F (long date+time), R (relative, e.g. "3 hours ago"). Defaults to R.
string: Markup such as "<t:1700000000:R>".parseDuration(text): number
Parses a human duration into MINUTES, so it feeds straight into timeout(). Accepts a run of amount+unit pairs using s/m/h/d/w, e.g. "10m", "2h30m", "7d". A bare number is read as minutes. Throws when it can't parse, so a bad slash-command argument fails loudly rather than timing someone out for the wrong span. No Discord call.
text(string): A duration such as "10m", "2h30m" or "7d".
number: The duration in minutes (fractional for seconds).Events(4)
listScheduledEvents(): ScheduledEventContext[]
Returns the server's scheduled events, the read that createScheduledEvent() / modifyScheduledEvent() / deleteScheduledEvent() never had. Same shape as ctx.scheduledEvent. Live REST read, so it doesn't spend the hourly action budget.
ReturnsScheduledEventContext[]: Array of the server's scheduled events.createScheduledEvent(optionsJson, kvKey?): void
Creates a guild scheduled event. optionsJson is a JSON object: {name, description, entityType ("external"|"voice"|"stage"), channelId, location, startsAt (ISO 8601), endsAt (ISO 8601)}. External events need location + endsAt; voice/stage events need channelId. When kvKey is set, the new event's ID is written there.
optionsJson(string): JSON event definition (see description).kvKey(string, optional): KV key to store the new event ID under (empty to skip).
modifyScheduledEvent(eventId, optionsJson): void
Edits an existing scheduled event. optionsJson uses the same shape as createScheduledEvent, plus an optional status ("active"|"completed"|"canceled") to transition it: flip a newly-created event to "active" so it shows as happening now, or "completed" when it ends. Only the fields you set change.
eventId(string): Discord snowflake ID of the scheduled event.optionsJson(string): JSON of the fields to change.
deleteScheduledEvent(eventId): void
Deletes a scheduled event.
eventId(string): Discord snowflake ID of the scheduled event.
Data types
Shapes referenced by the functions above as parameters or return values. See the ctx object for ForumTag and every ctx.*-nested type.
CachedMessage
A message returned by getMessage()/getMessages() (fetched live over REST).
- id: string
- channelId: string
- authorId: string
- authorUsername: string
- authorAvatarUrl: string
- content: string
- attachments: AttachmentContext[]
- referencedMessageId: string
- isForwarded: boolean
- createdAt: string (ISO 8601 creation timestamp.)
- editedAt: string | null (ISO 8601 time of the last edit, or null if never edited.)
- authorIsBot: boolean
- mentionedUserIds: string[]
- mentionedRoleIds: string[]
- mentionsEveryone: boolean
- isPinned: boolean
- reactions: MessageReaction[]
MessageReaction
A per-emoji reaction summary on a CachedMessage (a count, not the reactor list; see getReactionUsers()).
- emojiId: string | null (Custom emoji ID (null for a unicode emoji).)
- emojiName: string (Emoji name, or the unicode character itself for a unicode emoji.)
- animated: boolean
- count: number
- me: boolean (Whether the bot itself has reacted with this emoji.)
MemberSummary
A cached guild member returned by listMembers() / getMember().
- userId: string
- username: string
- displayName: string (Nickname, else global display name, else username.)
- nickname: string
- avatarUrl: string (Server avatar, else global avatar, else Discord's default. Never empty.)
- joinedAt: string (ISO 8601 join timestamp (empty when unknown).)
- roleIds: string[]
RoleSummary
A cached guild role returned by listRoles().
- id: string
- name: string
ChannelSummary
A cached guild channel returned by listChannels().
- id: string
- name: string
BanSummary
One entry of the server's ban list, returned by getBans().
- userId: string
- username: string
- displayName: string (Global display name, falling back to the username.)
- reason: string (The reason recorded with the ban (empty when none was given).)
RoleInfo
A role, returned by createRole() and modifyRole(). Richer than the cache-backed RoleSummary that listRoles() returns, which has only id and name.
- id: string
- name: string
- colorArgb: number (Colour as a number; 0 means no colour (Discord renders default grey).)
- position: number (Where it sits in the role list. Higher outranks lower.)
- hoist: boolean (Shown separately in the member list.)
- mentionable: boolean
- managed: boolean (True for a role Discord manages itself (a bot's, an integration's, the booster role). Nobody can add or remove one, so skip these.)
ChannelInfo
One channel's detail, returned by getChannel().
- id: string
- name: string
- type: string ("Text", "Voice", "Category", "Announcement", "Forum", "MediaForum", "Stage", "Thread" or "Unknown".)
- topic: string (Empty when unset or when the kind has no topic.)
- parentId: string (The category it sits under, or the parent channel for a thread (empty at top level).)
- position: number
- nsfw: boolean
- slowmodeSeconds: number (Slowmode in seconds (0 when off).)
EmojiSummary
A custom emoji in the server, returned by getEmojis().
- id: string
- name: string
- animated: boolean
- available: boolean (False when the server lost boosts and this emoji is over its new limit.)
- mention: string (The "<:name:id>" form you paste into message content to render it.)
- imageUrl: string (The image URL, for an embed.)
InviteSummary
One of the server's active invites, returned by getInvites().
- code: string
- channelId: string
- inviterId: string (Who created it (empty when Discord doesn't say, e.g. a vanity URL).)
- inviterUsername: string
- uses: number (Times used so far: the field an invite tracker diffs.)
- maxUses: number (Max uses before it expires (0 = unlimited).)
- maxAge: number (Lifetime in seconds (0 = never expires).)
- temporary: boolean
- createdAt: string (ISO 8601 creation time (empty when unknown).)
- expiresAt: string (ISO 8601 expiry (empty when it never expires).)
PinnedMessage
A pinned message returned by getPins().
- id: string
- channelId: string
- authorId: string
- content: string
FetchResponse
Result of a fetch() call.
- status: number
- headers: Record<string, string>
- body: string (Raw response body as a string, so parse JSON/XML yourself.)
EmbedData
A rich embed. Every field is optional, so set only what you need.
- title?: string
- description?: string
- colorArgb?: number (Colour of the left-hand bar, as a decimal number (e.g. 0xED4245 / 15548997 for red).)
- authorName?: string
- authorIconUrl?: string
- footerText?: string
- footerIconUrl?: string
- timestamp?: string (ISO-8601 timestamp, e.g. new Date().toISOString().)
- imageUrl?: string
ButtonData
A button attached to a message.
- label: string
- style?: "primary" | "secondary" | "success" | "danger"
- payload?: string (Passed to the handling script as ctx.interaction.payload (max 50 chars).)
- handler?: string (Name of the script that runs when the button is clicked. A "Button Click" script replies; a "Button Click: Modal" script opens its form instead.)