Mallard

Script functions

58 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(4)

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.
Channel(8)

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

renameChannel(channelId, name): void

Renames a channel or category.

  • channelId (string): Discord snowflake ID of the channel or category.
  • name (string): The new name.

getForumTags(channelId): ForumTag[]

Returns the tags defined on a forum or media channel (the set a post can be tagged with), each as { id, name, emojiName, emojiId, moderated }; see ForumTag below. Pair with ctx.thread.tags (the tags actually applied to a new post) on a ThreadCreate script, or call 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.
Messaging(7)

sendMessage(channelId, content, embeds?, buttons?, pings?, replyToMessageId?): void

Sends a message to a channel. embeds and buttons are plain arrays, no JSON.stringify. An embed is EmbedData; a button is ButtonData, both below. handler names the script that runs when the button is clicked: a "Button Click" script replies, a "Button Click: Modal" script opens its form instead. payload comes back to the handling script as ctx.interaction.payload (max 50 chars).

  • 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 for a silent mention.
  • 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.

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 your server. 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 your server).
  • 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 your server).

sendDm(userId, content, embeds?, buttons?, pings?): boolean

Sends a direct message to a user. DMs always carry an embed attributing them to your server. Returns false rather than failing the script if the user doesn't accept DMs from your server, has blocked the bot, or shares no mutual guild; check the result if you want to mention it (the seeded /ban does: it DMs the appeal link before banning, then tells the moderator when the DM didn't land).

  • 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 actually notify. Defaults to true; pass false for a silent mention.
Returns boolean: true if the DM was delivered, false if the user doesn't accept DMs, 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 actually notify. Defaults to true; pass false for 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; note there's no buttons parameter at all, unlike every other send function. channelId may be a thread — 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 actually notify. Defaults to true; pass false for 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 (see ctx.restActionsRemaining). 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 (see ctx.restActionsRemaining). 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 (see ctx.restActionsRemaining). 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.
Threads(7)

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.

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.
Members(9)

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

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 — no call 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.
Events(3)

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 -- e.g. 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.
HTTP(1)

fetch(url, optionsJson?): FetchResponse

Performs an HTTP request to an external API or feed and returns {status, headers, body} (body is a string; 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 optionsJson.timeoutMs), 1 MB response cap, 30 requests/minute per guild plus an hourly budget (see ctx.fetchCallsRemaining), and private/internal addresses are blocked. optionsJson is a 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. Full detail on Subsystems & limits.

  • url (string): Absolute https URL to request.
  • optionsJson (string, optional): JSON-serialised options ({method, headers, body}), or empty string for a plain GET.
Returns FetchResponse: Object with status (number), headers (Record<string,string>) and body (string).
Roles(2)

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.
KV Store(3)

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 immediately, the moment this call returns: 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.
Utility(1)

log(message): void

Logs a message to the bot's server log.

  • message (string): Message to log.

Data types

Shapes referenced by the functions above as parameters or return values. See the ctx object for ForumTag and every ctx.*-nested type.

ScriptArgs

The shape of ctx.args; see Commands & modals for how command options become these.

  • [optionName: string]: string (every value arrives as a string, regardless of the Discord option type)
EmbedData

A rich embed passed to any send/edit function. Every field is optional; set only what you need.

  • title?: string
  • description?: string
  • colorArgb?: number (colour of the left-hand bar, as a decimal number, e.g. 15548997 for red)
  • authorName?: string
  • authorIconUrl?: string
  • footerText?: string
  • footerIconUrl?: string
  • timestamp?: string (ISO 8601, 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 clicked; a "Button Click" script replies, a "Button Click: Modal" script opens its form instead)
CachedMessage

A message returned by getMessage()/getMessages(), fetched live over REST despite the name.

  • id: string
  • channelId: string
  • authorId: string
  • authorUsername: string
  • authorAvatarUrl: string
  • content: string
  • attachments: AttachmentContext[]
  • referencedMessageId: string
  • isForwarded: boolean
  • createdAt: string (ISO 8601 creation timestamp)
  • reactions: MessageReaction[]
MessageReaction

A per-emoji reaction summary on a CachedMessage (count, not the reactor list — see getReactionUsers()).

  • emojiId: string | null (custom emoji ID; null for a unicode emoji)
  • emojiName: string (name, or the unicode character itself)
  • animated: boolean
  • count: number
  • me: boolean (whether the bot itself has reacted)
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; 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
PinnedMessage

A pinned message returned by getPins().

  • id: string
  • channelId: string
  • authorId: string
  • content: string
FetchResponse

The result of a fetch() call.

  • status: number
  • headers: Record<string, string>
  • body: string (raw response body; parse JSON/XML yourself)
An unhandled error has occurred. Reload 🗙