Mallard

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.
Returns 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.
Returns 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.
Returns 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.
Returns 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).
Returns 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.
Returns 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.
Returns 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.

Returns ChannelSummary[]: 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.
Returns 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.
Returns 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.

Returns EmojiSummary[]: 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.
Returns 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.
Returns 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.
Returns 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).
Returns 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.
Returns 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).
Returns 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.
Returns 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.
Returns 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).
Returns 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".
Returns 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.
Returns 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.
Returns 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.

Returns InviteSummary[]: 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.
Returns MemberSummary | null: The member, or null when not cached.

listRoles(): RoleSummary[]

Lists the guild's cached roles (id + name). Cheap cached read (no REST).

Returns RoleSummary[]: 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.
Returns 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.
Returns 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.
Returns 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.
Returns 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.
Returns 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.
Returns 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).
Returns 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.
Returns 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".
Returns 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.

Returns ScheduledEventContext[]: 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.)
An unhandled error has occurred. Reload 🗙