Script functions
142 global functions available to scripts, grouped by
category. Functions marked ⚡ async return a Promise and
must be awaited (await ctx.channel.send(...)); unmarked functions (log,
require) are synchronous. Sending to a channel, replying to an interaction, and
messaging from a trigger with no ctx.channel are covered under
Sending messages. Almost all are
available on every trigger (the
interaction replies only where there is an interaction to answer); the dashboard's autocomplete
only offers what's valid for the script you're editing. Where Discord requires Mallard to hold a
permission, it is listed, and the Scripts page warns when Mallard's role lacks one a script needs.
Functions
Channel(22)
getForumTags(channelId): ForumTag[]⚡ async
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 Promise<ForumTag[]>: Array of the forum's available tags.
createChannel(name, type, parentIdOrOptions?, topic?, reason?): string⚡ async
Creates a channel in the guild and returns its snowflake ID. Pass an options object as the third argument to set everything at once, including permission overwrites, which is how a private ticket channel is made in a single step: createChannel("ticket-42", "text", { parentId, overwrites: [{ targetId: ctx.guildId, deny: ["ViewChannel"] }, { targetId: ctx.actorId, targetIsRole: false, allow: ["ViewChannel", "SendMessages"] }] }).
Mallard needs: ManageChannels
name(string): Name for the new channel.type(string): Channel kind: "text", "voice", "category", "announcement", "forum", "media", or "stage".parentIdOrOptions(string | ChannelOptions, optional): Category ID to nest under (empty for none), or an options object: {parentId, topic, position, nsfw, slowmode, userLimit, bitrate, overwrites: [{targetId, targetIsRole?, allow?, deny?}]}.topic(string, optional): Channel topic/description (empty for none).reason(string, optional): Audit log reason.
Returns Promise<string>: The created channel's snowflake ID.
deleteChannel(channelId, reason?): void⚡ async
Deletes a channel (or thread) in the guild.
Mallard needs: ManageChannels
channelId(string): Discord snowflake ID of the channel to delete.reason(string, optional): Audit log reason.
modifyChannel(channelId, nameOrOptions, topic?, reason?): void⚡ async
Changes a channel. The short form renames and/or re-topics it: an empty name leaves the name alone, while an empty topic CLEARS the topic (pass null or omit it to leave the topic alone). Pass an options object as the second argument to change anything else: modifyChannel(id, { parentId, position, userLimit, bitrate, nsfw, slowmode, syncPermissions: true }). Works on threads too (name, slowmode, autoArchiveMinutes).
Mallard needs: ManageChannels
channelId(string): Discord snowflake ID of the channel.nameOrOptions(string | ChannelOptions): New name (empty to leave unchanged), or an options object: {name, topic, parentId, position, nsfw, slowmode, userLimit, bitrate, syncPermissions, autoArchiveMinutes}.topic(string | null, optional): New topic (empty to clear, null/omitted to keep).reason(string, optional): Audit log reason.
setChannelTopic(channelId, topic, reason?): void⚡ async
Sets a channel's topic/description.
Mallard needs: ManageChannels
channelId(string): Discord snowflake ID of the channel.topic(string): The new topic (empty to clear).reason(string, optional): Audit log reason.
listChannels(): ChannelSummary[]⚡ async
Lists the guild's cached channels (id, name, type, category, position; no threads). Cheap cached read (no REST), like listMembers. type and parentId fill in when the bot next reconnects for channels cached before this field existed.
Returns Promise<ChannelSummary[]>: Array of the guild's channels.
getChannel(channelId): ChannelInfo | null⚡ async
Returns one channel's detail (name, type, topic, parent, position, nsfw, slowmode, user limit, bitrate), or null when it doesn't exist. Read from the bot's channel cache, which gateway events keep current; a REST read only for a channel the cache hasn't fully recorded yet.
channelId(string): Discord snowflake ID of the channel.
Returns Promise<ChannelInfo | null>: The channel, or null when it no longer exists.
setChannelPermission(channelId, targetId, allow, deny, reason?, targetIsRole?): boolean⚡ async
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).
Mallard needs: ManageRoles
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. Discord's own spelling ("SEND_MESSAGES") works too.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 Promise<boolean>: True once applied.
getEmojis(): EmojiSummary[]⚡ async
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.
Returns Promise<EmojiSummary[]>: Array of the server's custom emoji.
deleteEmoji(emojiId, reason?): boolean⚡ async
Deletes a custom emoji from the server.
Mallard needs: ManageGuildExpressions
emojiId(string): Discord snowflake ID of the emoji.reason(string, optional): Audit log reason.
Returns Promise<boolean>: False when the emoji was already gone.
getChannelPermissions(channelId): PermissionOverwrite[]⚡ async
Lists a channel's permission overwrites: which roles and members are explicitly allowed or denied what. Useful to check whether a channel is locked, or to copy its permissions.
channelId(string): The channel.
Returns Promise<PermissionOverwrite[]>: The channel's overwrites.
setVoiceStatus(channelId, status): void⚡ async
Sets the status line shown under a voice channel's name (up to 500 characters), e.g. "🎮 Game night". Empty clears it.
Mallard needs: SetVoiceChannelStatus
channelId(string): The voice channel.status(string): The status text (empty to clear).
createEmoji(name, mediaId, reason?): EmojiSummary⚡ async
Creates a custom emoji from an image on the server's Media page (PNG, JPEG, WebP or GIF, 256 KB at most) and returns it. Only the Media page is a source: Mallard never fetches an arbitrary URL for this.
Mallard needs: CreateGuildExpressions
name(string): Emoji name (2-32 letters, numbers or underscores).mediaId(string): ID of an image on the Media page.reason(string, optional): Audit log reason.
Returns Promise<EmojiSummary>: The created emoji.
getIntegrations(): IntegrationSummary[]⚡ async
Lists the server's integrations: bots, Twitch/YouTube subscription roles and other connections. Pair with the GuildIntegrationsUpdate trigger.
Mallard needs: ManageGuild
Returns Promise<IntegrationSummary[]>: The server's integrations.
startStage(channelId, topic, options?): string⚡ async
Starts a stage in a stage channel with a topic and returns the stage instance's ID. options.notify pings @everyone that the stage started.
Mallard needs: ManageChannels, MuteUsers, MoveUsers
channelId(string): The stage channel.topic(string): What the stage is about (1-120 characters).options({ notify?: boolean }, optional): Extras.
Returns Promise<string>: The stage instance ID.
modifyStage(channelId, topic): void⚡ async
Changes the topic of a live stage.
Mallard needs: ManageChannels, MuteUsers, MoveUsers
channelId(string): The stage channel.topic(string): The new topic.
endStage(channelId): boolean⚡ async
Ends the live stage in a stage channel.
Mallard needs: ManageChannels, MuteUsers, MoveUsers
channelId(string): The stage channel.
Returns Promise<boolean>: False when no stage was live.
lockChannel(channelId, reason?): boolean⚡ async
Locks a channel by denying the @everyone role the SendMessages permission.
Mallard needs: ManageRoles, ManageChannels
channelId(string): Discord snowflake ID of the channel to lock.reason(string, optional): Audit log reason.
Returns Promise<boolean>: Always true when the permission overwrite is applied.
unlockChannel(channelId, reason?): boolean⚡ async
Unlocks a channel by clearing the SendMessages deny overwrite on the @everyone role.
Mallard needs: ManageRoles, ManageChannels
channelId(string): Discord snowflake ID of the channel to unlock.reason(string, optional): Audit log reason.
Returns Promise<boolean>: Always true when the permission overwrite is updated.
setChannelNsfw(channelId, nsfw?, reason?): boolean⚡ async
Sets whether a channel is marked as Age-Restricted (NSFW).
Mallard needs: ManageChannels
channelId(string): Discord snowflake ID of the channel.nsfw(boolean, optional): Whether the channel should be NSFW.reason(string, optional): Audit log reason.
Returns Promise<boolean>: Always true once updated.
deleteChannelPermission(channelId, targetId, reason?): boolean⚡ async
Deletes a permission overwrite for a role or member on a channel.
Mallard needs: ManageRoles
channelId(string): Discord snowflake ID of the channel.targetId(string): Discord snowflake ID of the role or member.reason(string, optional): Audit log reason.
Returns Promise<boolean>: Always true once deleted.
getEmoji(emojiId): EmojiSummary?⚡ async
Fetches metadata for a single custom emoji in the guild by ID.
emojiId(string): Discord snowflake ID of the emoji.
Returns Promise<EmojiSummary?>: The emoji metadata, or null if not found.
Threads(7)
setForumTags(threadId, tagIds, reason?): void⚡ async
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.
Mallard needs: ManageThreads
threadId(string): Discord snowflake ID of the forum post (thread).tagIds(string[] | string): Tag IDs to apply, as an array or a JSON array string.reason(string, optional): Audit log reason.
deleteThread(threadId, reason?): void⚡ async
Deletes a thread / forum post.
Mallard needs: ManageThreads
threadId(string): Discord snowflake ID of the thread to delete.reason(string, optional): Audit log reason.
createThread(channelId, name, isPrivate, userIds?, initialMessage?, options?): string⚡ async
Creates a thread under a channel, optionally adding initial members and an opening message. Returns the created thread's snowflake ID. On a forum or media channel this creates a post, using initialMessage (or the name, when empty) as its body; createForumPost() gives more control there. To start a thread from an existing message, use createThreadFromMessage().
Mallard needs: CreatePublicThreads, CreatePrivateThreads
channelId(string): Discord snowflake ID of the parent channel.name(string): Thread name.isPrivate(boolean): Create a private thread.userIds(string | string[], optional): User IDs to add, as an array or a comma-separated string (empty for none).initialMessage(string, optional): Message posted into the thread after creation, or empty string.options({ autoArchiveMinutes?: number, slowmode?: number, tagIds?: string[], reason?: string }, optional): autoArchiveMinutes is 60, 1440, 4320 or 10080; tagIds applies to forum posts.
Returns Promise<string>: The created thread's snowflake ID.
createForumPost(forumId, title, content, options?): string⚡ async
Creates a post in a forum or media channel and returns its thread ID. The body is a string or a MessageOptions object, so a post can carry embeds, buttons and files; options.tagIds applies forum tags (see getForumTags()).
Mallard needs: SendMessages, CreatePublicThreads
forumId(string): The forum or media channel.title(string): The post title.content(string | MessageOptions): The opening message.options({ tagIds?: string[], autoArchiveMinutes?: number, slowmode?: number, reason?: string }, optional): Extras.
Returns Promise<string>: The new post's thread ID.
createThreadFromMessage(channelId, messageId, name, options?): string⚡ async
Starts a thread from an existing message (the "Create Thread" item on a message) and returns the thread ID. Auto-thread every post in a channel from a MessageCreate script.
Mallard needs: CreatePublicThreads
channelId(string): The channel the message is in.messageId(string): The message to start from.name(string): Thread name (1-100 characters).options({ autoArchiveMinutes?: number, slowmode?: number, reason?: string }, optional): Extras.
Returns Promise<string>: The new thread's ID.
listActiveThreads(parentId?): ThreadSummary[]⚡ async
Lists the server's active (unarchived) threads and forum posts, optionally only those under one channel. For stale-ticket sweeps and forum housekeeping.
parentId(string, optional): Only threads in this channel or forum.
Returns Promise<ThreadSummary[]>: The active threads.
getThreadMembers(threadId): string[]⚡ async
Returns the user IDs who have joined a thread (up to 1000).
threadId(string): The thread.
Returns Promise<string[]>: Member user IDs.
Members(17)
hasRole(userId, roleId): boolean⚡ async
Returns true when the user currently has the given role. Backed by the bot's guild-member cache (loaded from the gateway whenever the bot connects and kept live by member events); 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 Promise<boolean>: True when the user has the role; false if not, or if they aren't a cached member.
listMembers(afterUserId?, limit?): MemberSummary[]⚡ async
Returns a page of cached guild members ordered by user ID. The member cache is loaded in full from the gateway whenever the bot connects and kept live by member events (requires the bot's Server Members privileged intent). Page with the last userId of the previous call.
afterUserId(string, optional): Return members with a user ID greater than this; empty or omitted starts from the beginning.limit(number, optional): Page size (1-500; defaults to 100).
Returns Promise<MemberSummary[]>: Array of cached members (empty when the cache has not been populated yet).
hasPermission(userId, permission): boolean⚡ async
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" (hasChannelPermission() does that). Like hasRole(), an uncached member returns false.
userId(string): Discord snowflake ID of the member.permission(string): Permission name, e.g. "ManageMessages", "BanMembers", "KickMembers", "ModerateMembers", "ManageRoles", "ManageChannels", "Administrator". Discord's own spelling ("BAN_MEMBERS") works too.
Returns Promise<boolean>: True when the member holds the permission.
createInvite(channelId, maxAgeSeconds, maxUses, temporary, reason?, options?): InviteSummary⚡ async
Creates an invite to a channel and returns it. options.roleIds makes it a community invite that gives those roles to whoever joins with it.
Mallard needs: CreateInstantInvite
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.options({ roleIds?: string[], unique?: boolean }, optional): roleIds: roles granted on join; unique: always mint a new code.
Returns Promise<InviteSummary>: The created invite, including its code.
deleteInvite(code, reason?): boolean⚡ async
Revokes one of this server's invites by its code. An invite belonging to another server is refused.
Mallard needs: ManageChannels
code(string): The invite code (the part after discord.gg/).reason(string, optional): Audit log reason.
Returns Promise<boolean>: False when the invite was already gone.
getInvites(): InviteSummary[]⚡ async
Returns the server's active invites, including how many times each has been used. This is the only way to read a use count, and Discord only reports uses when Mallard has the Manage Server permission (without it every uses is 0). To attribute a join, ctx.member.inviteCode on GuildMemberAdd is simpler. Live REST read.
Mallard needs: ManageGuild
Returns Promise<InviteSummary[]>: Array of the server's active invites.
listRoles(): RoleSummary[]⚡ async
Lists the guild's cached roles (id + name). Cheap cached read (no REST).
Returns Promise<RoleSummary[]>: Array of the guild's roles.
setNickname(userId, nickname, reason?): void⚡ async
Sets or clears a member's nickname (empty string resets it to their username).
Mallard needs: ManageNicknames
userId(string): Discord snowflake ID of the member.nickname(string): The new nickname (empty to reset).reason(string, optional): Audit log reason.
deafenMember(userId, deafened, reason?): void⚡ async
Server-deafens or undeafens a member in voice.
Mallard needs: DeafenUsers
userId(string): Discord snowflake ID of the member.deafened(boolean): true to deafen, false to undeafen.reason(string, optional): Audit log reason.
getUser(userId): UserSummary | null⚡ async
Looks up any Discord user by ID, member of this server or not: name, avatar, account age. Live REST read.
userId(string): Discord snowflake ID of the user.
Returns Promise<UserSummary | null>: The user, or null when the ID doesn't exist.
fetchMember(userId): MemberContext | null⚡ async
Reads a member live from Discord rather than the member cache, with every field (boost, timeout, pending, flags). For a member getMember() doesn't have yet, or when those fields matter. Null when they aren't in the server.
userId(string): Discord snowflake ID of the member.
Returns Promise<MemberContext | null>: The member, or null.
searchMembers(query, limit?): MemberSummary[]⚡ async
Finds members whose username or nickname starts with the query, e.g. for a text command that takes a name.
query(string): The start of a username or nickname.limit(number, optional): Maximum to return (1-100; defaults to 25).
Returns Promise<MemberSummary[]>: Matching members.
getMembersWithRole(roleId, limit?): MemberSummary[]⚡ async
Lists cached members who hold a role, e.g. to strip a role from everyone or count staff. Cache read, no REST.
roleId(string): Discord snowflake ID of the role.limit(number, optional): Maximum to return (1-1000; defaults to 100).
Returns Promise<MemberSummary[]>: The members holding the role.
getVoiceMembers(channelId): VoiceMember[]⚡ async
Lists who is in a voice or stage channel right now, from the live voice-state cache. The basis for join-to-create channels (delete when empty), voice XP and event attendance.
channelId(string): The voice or stage channel.
Returns Promise<VoiceMember[]>: Everyone connected to that channel.
getVoiceState(userId): VoiceMember | null⚡ async
Where a member is in voice right now, or null when they aren't connected.
userId(string): Discord snowflake ID of the member.
Returns Promise<VoiceMember | null>: Their voice state.
hasChannelPermission(userId, channelId, permission): boolean⚡ async
Whether a member holds a permission in one specific channel, applying the channel's overwrites the way Discord does (roles, then @everyone and member overwrites). The per-channel counterpart of hasPermission().
userId(string): Discord snowflake ID of the member.channelId(string): The channel.permission(string): Permission name, e.g. "SendMessages", "ViewChannel".
Returns Promise<boolean>: True when they have it in that channel.
setBotProfile(options): { nickname: string, avatarUrl: string }⚡ async
Changes how Mallard looks in this server only: nickname, avatar, banner and bio. Seasonal avatars on a cron schedule, or branding per event. Images come from the server's Media page. Pro and Ultra only, at most once every 10 minutes; the audit log names the script. Pass nick: "" to go back to the bot's own name.
Mallard needs: ChangeNickname
options({ nick?: string, avatarMediaId?: string, bannerMediaId?: string, bio?: string }): Only what you pass changes. nick up to 32 characters, bio up to 190; image IDs from the Media page.
Returns Promise<{ nickname: string, avatarUrl: string }>: The profile as Discord stored it.
Moderation(8)
getBans(limit?): BanSummary[]⚡ async
Returns the server's ban list (userId, username, displayName, reason), newest first. Live REST read.
Mallard needs: BanUsers
limit(number, optional): How many to return (1-1000; defaults to 100).
Returns Promise<BanSummary[]>: Array of bans, capped at the requested limit.
isBanned(userId): boolean⚡ async
Whether a user is currently banned from the server. Cheaper and clearer than scanning getBans(). Live REST read.
Mallard needs: BanUsers
userId(string): Discord snowflake ID of the user.
Returns Promise<boolean>: True when the user is banned.
bulkBan(userIds, deleteMessageSeconds?, reason?): BulkResult⚡ async
Bans up to 200 users in one call, the raid-cleanup primitive: far faster than 200 ban() calls. Returns who was banned and who couldn't be (already banned, or above Mallard in the role list).
Mallard needs: BanUsers, ManageGuild
userIds(string[]): Up to 200 user IDs.deleteMessageSeconds(number, optional): Seconds of their messages to delete (0-604800, i.e. up to 7 days). Defaults to 0.reason(string, optional): Audit log reason.
Returns Promise<BulkResult>: {succeeded, failed} user IDs.
pauseInvites(hours, reason?): void⚡ async
Pauses joining through invites for up to 24 hours (Discord's own raid-mode switch, shown to members as "invites paused"). 0 resumes them. The other pause (DMs) is left as it is.
Mallard needs: ManageGuild
hours(number): How long to pause (0-24; fractions allowed). 0 resumes.reason(string, optional): Audit log reason.
pauseDms(hours, reason?): void⚡ async
Stops members who aren't friends DMing each other through the server for up to 24 hours, which cuts off a raid's DM spam. 0 resumes. Moderators and bots are unaffected.
Mallard needs: ManageGuild
hours(number): How long to pause (0-24; fractions allowed). 0 resumes.reason(string, optional): Audit log reason.
setVerificationLevel(level, reason?): void⚡ async
Changes the server's verification level, raid mode's other half: "high" makes new accounts wait 10 minutes in the server before talking.
Mallard needs: ManageGuild
level(string): "none", "low", "medium", "high" or "veryHigh".reason(string, optional): Audit log reason.
getAuditLog(query?): AuditLogEntry[]⚡ async
Reads recent audit-log entries, newest first: who banned someone, who deleted a channel. Filter with { actionType: "GuildUserBanAdd", userId, targetId, limit }.
Mallard needs: ViewAuditLog
query({ actionType?: string, userId?: string, targetId?: string, limit?: number }, optional): actionType uses the names in AuditLogEntry.actionType; userId is who acted; limit is 1-100 (default 25).
Returns Promise<AuditLogEntry[]>: Matching entries.
canModerate(userId): boolean⚡ async
Whether Mallard can act on a member at all: they aren't the owner, and their highest role sits below Mallard's. Check it before ban/kick/timeout/role changes to give a clear message instead of a failed call.
userId(string): Discord snowflake ID of the member.
Returns Promise<boolean>: True when Mallard outranks them.
Roles(8)
createRole(name, colorArgb?, hoist?, mentionable?, reason?): RoleInfo⚡ async
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.
Mallard needs: ManageRoles
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 Promise<RoleInfo>: The created role.
modifyRole(roleId, name?, colorArgb?, hoist?, mentionable?, reason?): boolean⚡ async
Renames or recolours a role. Omitted parameters are left unchanged. Permissions are not settable here, for the same reason as createRole().
Mallard needs: ManageRoles
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 Promise<boolean>: False when the role no longer exists.
deleteRole(roleId, reason?): boolean⚡ async
Deletes a role from the server.
Mallard needs: ManageRoles
roleId(string): Discord snowflake ID of the role.reason(string, optional): Audit log reason.
Returns Promise<boolean>: False when the role was already gone.
getRole(roleId): RoleInfo | null⚡ async
Reads one role live, with its permissions and position.
roleId(string): Discord snowflake ID of the role.
Returns Promise<RoleInfo | null>: The role, or null when it no longer exists.
getRoleMemberCounts(): Record<string, number>⚡ async
How many members hold each role, straight from Discord without walking the member list. Keyed by role ID (@everyone is left out).
Returns Promise<Record<string, number>>: Role ID → member count.
addRoleToMany(userIds, roleId, reason?): BulkResult⚡ async
Adds one role to many members at once (up to 500), running the calls in parallel, which is far quicker than a loop of addRole(). Returns who got it and who didn't (left the server, or above Mallard).
Mallard needs: ManageRoles
userIds(string[]): Members to give the role to.roleId(string): The role.reason(string, optional): Audit log reason.
Returns Promise<BulkResult>: {succeeded, failed} user IDs.
removeRoleFromMany(userIds, roleId, reason?): BulkResult⚡ async
Removes one role from many members at once (up to 500), in parallel. Pair with getMembersWithRole() to clear a role from everyone.
Mallard needs: ManageRoles
userIds(string[]): Members to take the role from.roleId(string): The role.reason(string, optional): Audit log reason.
Returns Promise<BulkResult>: {succeeded, failed} user IDs.
bulkRole(userIds, roleId, reason?): object⚡ async
Alias for addRoleToMany(). Adds a role to multiple members.
Mallard needs: ManageRoles
userIds(string[]): Array of member IDs.roleId(string): Discord snowflake ID of the role to assign.reason(string, optional): Audit log reason.
Returns Promise<object>: Object with succeeded and failed arrays of user ID strings.
Events(5)
listScheduledEvents(): ScheduledEventContext[]⚡ async
Returns the server's scheduled events, the read that createScheduledEvent() / modifyScheduledEvent() / deleteScheduledEvent() never had. Same shape as ctx.scheduledEvent. Live REST read.
Returns Promise<ScheduledEventContext[]>: Array of the server's scheduled events.
createScheduledEvent(options): string⚡ async
Creates a guild scheduled event. options is an object (or JSON string): {name, description, entityType ("external"|"voice"|"stage"), channelId, location, startsAt (ISO 8601), endsAt (ISO 8601), recurrence, coverMediaId, reason}. External events need location + endsAt; voice/stage events need channelId. recurrence makes it repeat: {frequency: "daily"|"weekly"|"monthly"|"yearly", interval?, byWeekday?: ["monday", …]} (Discord allows daily, weekly on one day, every other week, monthly on a date or weekday, yearly). coverMediaId is an image from the Media page. Returns the created event's snowflake ID.
Mallard needs: CreateEvents
options(object | string): The event definition (see description).
Returns Promise<string>: The created scheduled event's snowflake ID.
modifyScheduledEvent(eventId, options): void⚡ async
Edits an existing scheduled event. options 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.
Mallard needs: ManageEvents
eventId(string): Discord snowflake ID of the scheduled event.options(object | string): The fields to change.
deleteScheduledEvent(eventId, reason?): void⚡ async
Deletes a scheduled event.
Mallard needs: ManageEvents
eventId(string): Discord snowflake ID of the scheduled event.reason(string, optional): Audit log reason.
getScheduledEventUsers(eventId, max?): string[]⚡ async
Returns the user IDs who RSVP'd to a scheduled event (up to 1000), e.g. to ping them 15 minutes before it starts.
eventId(string): The scheduled event.max(number, optional): Maximum to return (1-1000; defaults to 100).
Returns Promise<string[]>: Interested user IDs.
AutoMod(5)
listAutoModRules(): AutoModRule[]⚡ async
Lists the server's AutoMod rules (Discord's own filter, which blocks a message before anyone sees it and costs no runtime). createdByBot marks the ones Mallard's scripts made. Live REST read.
Mallard needs: ManageGuild
Returns Promise<AutoModRule[]>: The rules.
getAutoModRule(ruleId): AutoModRule | null⚡ async
One AutoMod rule.
Mallard needs: ManageGuild
ruleId(string): The rule's ID.
Returns Promise<AutoModRule | null>: The rule, or null when it doesn't exist.
createAutoModRule(options): string⚡ async
Creates an AutoMod rule: setup wizards, a /raidmode toggle, word lists synced from the key-value store. Discord allows 6 keyword rules and one each of spam, preset, mentionSpam and memberProfile. Rules that would match every message (an empty-matching regex, the keyword "*") are refused. The audit log names the script. A run can create, change or delete 5 rules.
Mallard needs: ManageGuild
options(AutoModRuleOptions): e.g. { name: "No invites", trigger: "keyword", keywords: ["discord.gg/*"], actions: [{ type: "block", message: "No invite links" }] }.
Returns Promise<string>: The new rule's ID.
modifyAutoModRule(ruleId, options): void⚡ async
Changes an AutoMod rule. Only the options you pass change; pass enabled: false to switch a rule off. A rule's trigger can't be changed.
Mallard needs: ManageGuild
ruleId(string): The rule's ID.options(AutoModRuleOptions): What to change.
deleteAutoModRule(ruleId, reason?): boolean⚡ async
Deletes an AutoMod rule.
Mallard needs: ManageGuild
ruleId(string): The rule's ID.reason(string, optional): Audit log reason.
Returns Promise<boolean>: False when it was already gone.
Messaging(9)
forwardMessage(sourceChannelId, messageId, targetChannelId): string⚡ async
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. Returns the forward's message ID. Both channels must belong to this guild. Discord refuses to forward polls, calls, and system messages, and refuses any message whose content the bot can't read (error 160014); either ends the script unless caught, so guard with getMessage first if the source might be gone. To add a comment, send it as a separate sendMessage.
Mallard needs: SendMessages, ReadMessageHistory
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).
Returns Promise<string>: The forward's message ID.
forwardDm(targetChannelId): string⚡ async
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. Returns the forward's message ID. 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. 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).
Returns Promise<string>: The forward's message ID.
sendWebhookMessage(channelId, username, avatarUrl, content, embeds?, pings?, options?): string⚡ async
Sends a message through a channel webhook with a custom username and avatar (persona messages) and returns its ID, which editWebhookMessage()/deleteWebhookMessage() take. The webhook is created on first use and belongs to Mallard, so its messages can carry buttons and select menus like any other. channelId may be a thread, in which case the webhook is created on its parent channel and the message is posted into the thread; to start a new forum post instead, pass the forum's ID with options.threadName.
Mallard needs: ManageWebhooks
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.options({ threadName?: string, tagIds?: string[], buttons?: ButtonData[], selects?: SelectMenuData[], files?: FileData[], silent?: boolean }, optional): Extras: threadName (with a forum channelId) starts a new post, tagIds tags it, plus components, files and @silent.
Returns Promise<string>: The sent message's snowflake ID.
sendPoll(channelId, question, answers, durationHours?, allowMultiselect?): string⚡ async
Sends a native Discord poll to a channel and returns its message ID (what endPoll() and getPollResults() take). answers is an array of 1-10 answers, each a string or { text, emoji }; a JSON array string still works.
Mallard needs: SendPolls
channelId(string): Discord snowflake ID of the channel.question(string): The poll question.answers((string | { text: string, emoji?: string })[] | string): The answers (1-10).durationHours(number, optional): How long the poll runs, in hours (1-768; defaults to 24).allowMultiselect(boolean, optional): Whether voters can pick multiple answers. Defaults to false.
Returns Promise<string>: The poll message's snowflake ID.
endPoll(channelId, messageId): void⚡ async
Immediately closes a poll the bot posted.
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the poll message.
triggerTyping(channelId): void⚡ async
Shows "Mallard is typing…" in a channel for about 10 seconds, or until the next message it sends. Cosmetic, for replies that take a while (an AI call through fetch()).
Mallard needs: SendMessages
channelId(string): Discord snowflake ID of the channel.
editWebhookMessage(channelId, messageId, content, embeds?): void⚡ async
Edits a message sendWebhookMessage() posted. Like editMessage it replaces the message wholesale.
Mallard needs: ManageWebhooks
channelId(string): The channel (or thread) it was posted in.messageId(string): The ID sendWebhookMessage() returned.content(string | MessageOptions): New content, or a MessageOptions object.embeds(EmbedData[], optional): Replacement embeds.
deleteWebhookMessage(channelId, messageId): boolean⚡ async
Deletes a message sendWebhookMessage() posted.
Mallard needs: ManageWebhooks
channelId(string): The channel (or thread) it was posted in.messageId(string): The ID sendWebhookMessage() returned.
Returns Promise<boolean>: False when it was already gone.
getStickers(): StickerSummary[]⚡ async
Returns the server's stickers, whose IDs MessageOptions.stickerIds takes. Live REST read.
Returns Promise<StickerSummary[]>: The server's stickers.
Messages(15)
getMessage(channelId, messageId): CachedMessage | null⚡ async
Fetches a single message, or null if it no longer exists. A message the bot saw in the last 3 hours comes from its message cache; anything older, and any poll, is fetched live over REST, and that call's time counts toward the run like any other. The returned message's reactions field is a per-emoji count/me summary (see getReactionUsers for who reacted). 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 Promise<CachedMessage | null>: The message, or null when it doesn't exist.
getMessages(channelId, max?, options?): CachedMessage[]⚡ async
Fetches a channel's messages from Discord live over REST, newest first (up to 100 per call). With no options it returns the most recent ones; pass { before: messageId } to page further back, { after: messageId } to get newer ones, or { around: messageId } for context around a message. Each message's reactions field is a per-emoji count/me summary. The channel must belong to this guild.
Mallard needs: ReadMessageHistory
channelId(string): Discord snowflake ID of the channel (must be in this guild).max(number, optional): Maximum messages to return (1-100; defaults to 100).options({ before?: string, after?: string, around?: string }, optional): Page from a message ID instead of the newest message.
Returns Promise<CachedMessage[]>: Newest-first array of messages (empty when the channel has none).
bulkDelete(channelId, count, reason?): number⚡ async
Deletes the most recent N messages in a channel (1-100) and returns how many went. Messages older than 14 days are deleted individually, which is slower. To delete only some (one user's, or ones containing a word), use purge().
Mallard needs: ManageMessages, ReadMessageHistory
channelId(string): Discord snowflake ID of the channel.count(number): Number of recent messages to delete (1-100).reason(string, optional): Audit log reason.
Returns Promise<number>: How many messages were deleted.
addReaction(channelId, messageId, emoji): void⚡ async
Adds a reaction to a message as the bot. Emoji is a unicode emoji ("🦆") or a custom emoji as "name:id".
Mallard needs: AddReactions, ReadMessageHistory
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⚡ async
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 empty or omitted.
Mallard needs: ManageMessages
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, optional): User whose reaction to remove, or empty/omitted for all users.
getReactionUsers(channelId, messageId, emoji, max?, options?): string[]⚡ async
Returns the user IDs who reacted to a message with a specific emoji, up to 1000 per call (paged from Discord 100 at a time), which is enough for most giveaways; pass { after: lastUserId } to continue past that. Discord's endpoint is per-emoji, so read the emoji list off getMessage()'s reactions summary first. The channel must belong to this guild.
Mallard needs: ReadMessageHistory
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, optional): Maximum users to return (1-1000; defaults to 100).options({ after?: string, burst?: boolean }, optional): after: continue from this user ID; burst: list super-reactions instead of normal ones.
Returns Promise<string[]>: User IDs who reacted with that emoji (empty if none).
pinMessage(channelId, messageId, reason?): void⚡ async
Pins a message in its channel.
Mallard needs: PinMessages
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the message.reason(string, optional): Audit log reason.
unpinMessage(channelId, messageId, reason?): void⚡ async
Unpins a message in its channel.
Mallard needs: PinMessages
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the message.reason(string, optional): Audit log reason.
editMessage(channelId, messageId, content, embeds?, buttons?, pings?): void⚡ async
Edits a message the bot previously sent in a channel (keep the ID sendMessage returned). The edit fully replaces the message: embeds, buttons and menus you don't pass are removed. Pass a MessageOptions object as the third argument for select menus or files.
channelId(string): Discord snowflake ID of the channel.messageId(string): Discord snowflake ID of the message to edit.content(string | MessageOptions): New message content (empty string for embed-only), or a MessageOptions object.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[]⚡ async
Returns a channel's pinned messages (id, channelId, authorId, content). Live REST read.
channelId(string): Discord snowflake ID of the channel.
Returns Promise<PinnedMessage[]>: Array of the channel's pinned messages.
searchMessages(query): SearchResult⚡ async
Searches the whole server's message history, the way Discord's search bar does: by text, author, channel, mention, or what the message has (links, files, images, embeds, polls). Returns up to 25 matches per call plus the total. Discord builds the search index lazily, so a server that has never been searched may first throw "still indexing"; catch it and try again later.
Mallard needs: ReadMessageHistory
query(SearchQuery): {content?, authorId?, channelId?, mentions?, has?: "link"|"file"|"image"|"video"|"embed"|"poll"|"sticker", pinned?, sortBy?: "timestamp"|"relevance", limit? (1-25), offset?}
Returns Promise<SearchResult>: The total match count and the matching messages.
purge(channelId, options): number⚡ async
Deletes recent messages in a channel that match a filter, the classic /purge: purge(channelId, { count: 50, authorId }) removes that user's last 50. Scans back through up to 500 recent messages to find up to count matches (1-100). Returns how many were deleted.
Mallard needs: ManageMessages, ReadMessageHistory
channelId(string): Discord snowflake ID of the channel.options(PurgeOptions): {count, authorId?, contains?, bots? (only bot/webhook messages), attachments? (only ones with files), before?, reason?}
Returns Promise<number>: How many messages were deleted.
deleteMessages(channelId, messageIds, reason?): number⚡ async
Deletes specific messages from one channel in as few calls as possible (Discord's bulk delete for anything under 14 days old, one by one otherwise). Returns how many were deleted; IDs already gone are skipped.
Mallard needs: ManageMessages
channelId(string): Discord snowflake ID of the channel.messageIds(string[]): Up to 100 message IDs.reason(string, optional): Audit log reason.
Returns Promise<number>: How many messages were deleted.
getPollResults(channelId, messageId): PollData | null⚡ async
Reads a poll's current counts per answer (exact once isFinalized is true).
Mallard needs: ReadMessageHistory
channelId(string): Discord snowflake ID of the channel.messageId(string): The poll message's ID.
Returns Promise<PollData | null>: The poll, or null when the message has none.
getPollVoters(channelId, messageId, answerId, max?): string[]⚡ async
Returns the user IDs who voted for one poll answer (up to 1000).
Mallard needs: ReadMessageHistory
channelId(string): Discord snowflake ID of the channel.messageId(string): The poll message's ID.answerId(number): The answer's id (see getPollResults()).max(number, optional): Maximum voters to return (1-1000; defaults to 100).
Returns Promise<string[]>: Voter user IDs.
KV Store(9)
getKV(key, options?): string | null⚡ async
Retrieves a value from the guild's key-value store. Reads don't lock, so two runs at once can read the same value and the later setKV() overwrites the earlier one. For counters use incrKV(); for any other read-modify-write use updateKV(), or pass { lock: true } here, which holds the key until this run ends so no other run can change it in between. The reserved system key "env" is not accessible here; read it through ctx.env instead.
key(string): Key to look up.options({ lock?: boolean }, optional): lock: take the key's lock before reading, for a safe read-modify-write.
Returns Promise<string | null>: The stored JSON string, or null if not found / expired.
setKV(key, value, ttlSeconds?): void⚡ async
Stores a value in the guild's key-value store, overwriting any existing value. A string is stored as-is (it should be JSON, e.g. from JSON.stringify); any other value (object, array, number, boolean) is JSON-encoded for you. Written 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 key "env" cannot be written (it is managed on the dashboard's Database page).
key(string): Key to store under.value(any): A JSON string, or any value to be JSON-encoded.ttlSeconds(number, optional): Time-to-live in seconds; 0 or omitted for no expiry.
deleteKV(key): void⚡ async
Deletes a key from the guild's key-value store. The reserved system key "env" cannot be deleted.
key(string): Key to delete.
listKV(prefix?, limit?, afterKey?): string[]⚡ async
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, or use listKVEntries()/getKVMany(). This is what makes namespaced keys ("warns:123") usable as a collection. Page past the limit with afterKey. Expired keys and the reserved "env" row are never listed.
prefix(string, optional): Only keys starting with this. Empty or omitted lists everything.limit(number, optional): How many to return (1-1000; defaults to 100).afterKey(string, optional): Return keys after this one (the last key of the previous page).
Returns Promise<string[]>: Array of matching keys.
updateKV(key, fn, ttlSeconds?): any⚡ async
Safely read-modify-writes one key: takes the key's lock, calls fn with the current value (parsed from JSON, or null when missing), and stores what fn returns (undefined leaves it unchanged, null deletes it). No other run can change the key in between, so concurrent runs never lose each other's updates. Returns the stored value.
key(string): Key to update.fn((current: any) => any): Computes the new value from the current one.ttlSeconds(number, optional): Time-to-live for the new value (0 or omitted: none).
Returns Promise<any>: The new value (parsed).
incrKV(key, by?, ttlSeconds?): number⚡ async
Atomically adds to a number stored under a key (missing counts as 0) and returns the new total. The safe way to count: XP, message counts, ticket numbers.
key(string): Key holding the number.by(number, optional): Amount to add (may be negative; defaults to 1).ttlSeconds(number, optional): Time-to-live to (re)apply (0 or omitted: keep none).
Returns Promise<number>: The new total.
getKVMany(keys): Record<string, string | null>⚡ async
Reads many keys in one call, e.g. every key listKV() returned. Missing keys come back null.
keys(string[]): Up to 1000 keys.
Returns Promise<Record<string, string | null>>: Key → stored JSON string (or null).
ttlKV(key): number | null⚡ async
How many seconds a key has left before it expires: null when it never expires, -1 when it doesn't exist.
key(string): The key.
Returns Promise<number | null>: Seconds remaining.
listKVEntries(prefix?, limit?, afterKey?): KvEntry[]⚡ async
Like listKV() but returns each key's value and expiry too, for small collections (a leaderboard, a warn list) you'd otherwise read key by key.
prefix(string, optional): Only keys starting with this.limit(number, optional): How many to return (1-500; defaults to 100).afterKey(string, optional): Return keys after this one.
Returns Promise<KvEntry[]>: Keys with their values.
Scheduling(7)
runLater(scriptName, delaySeconds, payload?): string⚡ async
Runs another script (one with the Delayed trigger) after a delay, with a payload it reads from ctx.delayed.payload: the way to unban after 7 days, remove a temp role, send a reminder or end a giveaway. Returns a job ID for cancelLater(). The delay can be up to 90 days; a server can have at most 1000 pending jobs (Free: 100). The run is budgeted like any other when it comes due.
scriptName(string): Name of a script with the Delayed trigger.delaySeconds(number): How long to wait (1 second to 90 days).payload(any, optional): Anything JSON-serialisable (up to 4 KB).
Returns Promise<string>: The job's ID.
emit(eventName, payload?): boolean⚡ async
Sends a named event to every enabled CustomEvent script subscribed to it, with a payload they read from ctx.event.payload. The emitter needn't know who listens. Subscribers run within a couple of seconds, each budgeted like any other run. A run can emit 5 events, a server 60 a minute, and events can chain at most 3 deep.
eventName(string): e.g. "ticket.closed" (letters, digits and . _ : -).payload(any, optional): Anything JSON-serialisable (up to 16 KB).
Returns Promise<boolean>: True once the event is queued.
require(name): any
Loads a Library script and returns its module.exports. Each library runs once per run however often it's required. Pass the name as a plain string: libraries are looked up when the run is queued, so require(someVariable) can't find one.
name(string): Name of a script with the Library trigger.
Returns any: The library's module.exports.
cancelLater(jobId): boolean⚡ async
Cancels a pending runLater() job.
jobId(string): The ID runLater() returned.
Returns Promise<boolean>: False when the job had already run or didn't exist.
listLater(scriptName?): PendingJob[]⚡ async
Lists the server's pending runLater() jobs, soonest first.
scriptName(string, optional): Only jobs for this script.
Returns Promise<PendingJob[]>: Pending jobs.
expectReply(userId, channelId, scriptName, timeoutSeconds, payload?): void⚡ async
Waits for a user's next message in a channel and hands it to a ReplyReceived script, with a payload: the building block for step-by-step setups ("Which channel should I post in?"). If they don't answer within the timeout, the same script runs with ctx.reply.timedOut true. A new wait for the same user and channel replaces the old one.
userId(string): Whose reply to wait for.channelId(string): Where (a channel or thread in this server).scriptName(string): Name of a script with the ReplyReceived trigger.timeoutSeconds(number): How long to wait (5 seconds to 24 hours).payload(any, optional): Anything JSON-serialisable, read back as ctx.reply.payload.
cancelReply(userId, channelId): boolean⚡ async
Stops waiting for a user's reply in a channel.
userId(string): The user.channelId(string): The channel.
Returns Promise<boolean>: False when there was no wait.
Leaderboards(8)
zIncr(set, member, by?): number⚡ async
Adds to a member's score in a sorted set (a missing member starts at 0) and returns the new score. Atomic, so any number of runs can add at once without a lock: the way to keep XP, message counts or invite counts for a leaderboard. Each member costs its length plus 16 bytes of storage; a set holds up to 10,000 members on Free (25k Plus, 50k Pro, 100k Ultra) and a server can have 100 sets.
set(string): The set's name, e.g. "xp".member(string): Who or what is scored, usually a user ID (up to 100 characters).by(number, optional): Amount to add (may be negative; defaults to 1).
Returns Promise<number>: The member's new score.
zSet(set, member, score): number⚡ async
Sets a member's score outright, adding the member if needed.
set(string): The set's name.member(string): The member.score(number): The score.
Returns Promise<number>: The score.
zGet(set, member): number | null⚡ async
A member's score.
set(string): The set's name.member(string): The member.
Returns Promise<number | null>: The score, or null when the member isn't in the set.
zRank(set, member, options?): number | null⚡ async
A member's position, counting from 0 at the highest score (so rank + 1 is their place on the leaderboard). Pass { asc: true } to count from the lowest instead.
set(string): The set's name.member(string): The member.options({ asc?: boolean }, optional): Ascending order instead of descending.
Returns Promise<number | null>: 0-based rank, or null when the member isn't in the set.
zRange(set, start?, stop?, options?): string[] | { member: string, score: number }[]⚡ async
Members by rank, highest score first: zRange("xp", 0, 9, { withScores: true }) is the top ten. At most 100 per call; page with start/stop.
set(string): The set's name.start(number, optional): First rank (0-based; defaults to 0).stop(number, optional): Last rank, inclusive (defaults to start + 9).options({ asc?: boolean, withScores?: boolean }, optional): asc: lowest first. withScores: return { member, score } objects.
Returns Promise<string[] | { member: string, score: number }[]>: Members, or members with scores.
zRemove(set, member): boolean⚡ async
Removes a member from a set, e.g. when they leave the server.
set(string): The set's name.member(string): The member.
Returns Promise<boolean>: False when the member wasn't there.
zCount(set): number⚡ async
How many members a set has.
set(string): The set's name.
Returns Promise<number>: The count (0 for a set that doesn't exist).
zDelete(set): boolean⚡ async
Deletes a whole set, e.g. to reset a monthly leaderboard, and frees its storage.
set(string): The set's name.
Returns Promise<boolean>: False when there was no such set.
AI(2)
hasAi(): boolean
Returns true if the guild has an OpenRouter AI integration configured on the Integrations page.
Returns boolean: true if AI is configured, false otherwise.
aiChat(promptOrOptions, options?): AiChatResponse⚡ async
Sends a chat completion request to OpenRouter using the server's configured OpenRouter API key. Pass a prompt string or an options object ({ prompt, messages, model, maxTokens, temperature, reasoning, webSearch, webFetch, datetime, imageGen, advisor, subagent, tools, timeoutMs }). Server tools are enabled per request using shorthand boolean flags (e.g. webSearch: true, webFetch: true) or an explicit tools array. Returns { content, text, model, finishReason, usage }.
promptOrOptions(string | AiChatOptions): A prompt string, or an options object specifying messages, model, tools, or limits.options(AiChatOptions, optional): Optional options object when the first parameter is a prompt string.
Returns Promise<AiChatResponse>: Object with content (string), text (string), model (string), finishReason (string), and usage stats.
HTTP(1)
fetch(url, options?): FetchResponse⚡ async
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 30s 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; user-agent defaults to MallardBot/1.0), body, timeoutMs}. HEAD returns an empty body, handy for checking a URL exists.
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 Promise<FetchResponse>: Object with status (number), headers (Record<string,string>) and body (string).
Utility(14)
log(...values): void
Logs a line. 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. Takes any number of arguments like console.log, and objects and arrays are printed as JSON; console.log/info/warn/error work too (warn and error are marked as such). 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.
...values(any[]): What to log.
formatTimestamp(isoOrEpoch, style?, timeZone?): string
Turns an ISO 8601 timestamp (any of the ctx date fields), a Date or a unix-seconds number into Discord's timestamp markup, which renders in each reader's own timezone and language. This is the best way to show a time in a message: pass a timeZone to read what someone typed as their local time. 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.timeZone(string, optional): An IANA zone such as "Europe/London". With it, a time without an offset ("2026-10-02 20:00") or a phrase parseTime() understands ("fri 8pm", "tomorrow 9am") is read as local time there, so formatTimestamp(ctx.args.when, "F", "Europe/London") turns what an organiser typed into a time every reader sees in their own zone.
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. Seconds give a fraction ("30s" is 0.5), which timeout() accepts. Throws when it can't parse, so a bad slash-command argument fails loudly rather than timing someone out for the wrong span. Multiply by 60 for runLater()'s seconds. No Discord call.
text(string): A duration such as "10m", "2h30m" or "7d".
Returns number: The duration in minutes (fractional for seconds).
snowflakeTime(id): string
Turns any Discord ID (user, message, channel) into the ISO 8601 time it was created. A user's ID gives their account age. No Discord call.
id(string): A Discord snowflake ID.
Returns string: ISO 8601 timestamp.
randomInt(min, max): number
A cryptographically secure random whole number between min and max, both included. Use it instead of Math.random() for giveaways and anything people might dispute.
min(number): Smallest possible result.max(number): Largest possible result.
Returns number: The number.
randomUUID(): string
A random UUID (v4), for ticket IDs and unique keys.
Returns string: e.g. "3b241101-e2bb-4255-8caf-4136c566a962".
base64Encode(text): string
Encodes text (as UTF-8) in base64, e.g. for a Basic auth header: "Basic " + base64Encode(user + ":" + pass). Up to 64 KB.
text(string): The text.
Returns string: Base64 text.
base64Decode(text): string
Decodes base64 (standard or URL-safe, padding optional) back to UTF-8 text.
text(string): Base64 text.
Returns string: The decoded text.
sha256(text): string
The SHA-256 hash of some text (UTF-8, up to 64 KB), as lowercase hex.
text(string): The text.
Returns string: 64 hex characters.
hmacSha256(key, data, encoding?): string
Signs text with HMAC-SHA256, as signed APIs require. Also checks an incoming webhook's own signature scheme.
key(string): The signing key.data(string): The text to sign (up to 64 KB).encoding(string, optional): "hex" (default) or "base64".
Returns string: The signature.
encodeQuery(params): string
Builds a URL query string from an object: encodeQuery({ q: "duck facts", page: 2 }) is "q=duck%20facts&page=2". An array value repeats the key; null and undefined are skipped.
params(Record<string, any>): Keys and values.
Returns string: The query string, without a leading ?.
parseQuery(text): Record<string, string | string[]>
Reads a query string (or a whole URL, or a form-urlencoded body) into an object. A key that repeats becomes an array.
text(string): "a=1&b=2", "?a=1" or a URL.
Returns Record<string, string | string[]>: Keys and values.
splitMessage(text, maxLength?): string[]
Splits long text into chunks that fit in one message (2000 characters by default), breaking at line ends, then spaces, where it can. Send each chunk with sendMessage().
text(string): The text.maxLength(number, optional): Chunk size (100-4000; defaults to 2000).
Returns string[]: The chunks.
sleep(milliseconds): boolean⚡ async
Pauses script execution for the specified number of milliseconds (clamped between 0 and 30,000 ms / 30 seconds).
milliseconds(number): Number of milliseconds to pause (up to 30000).
Returns Promise<boolean>: Always true once the delay has elapsed.
Dates & time zones(5)
parseTime(text, timeZone?, from?): string | null
Reads a time the way people type it: "20:00", "8pm", "tomorrow 9am", "fri 8:30pm", "noon", "in 2h30m" or "2026-10-02 20:00", as local time in a zone. A time of day alone means its next occurrence. Pair it with formatTimestamp() so everyone sees it in their own zone, or with runLater() for a reminder (seconds = (Date.parse(iso) - Date.now()) / 1000).
text(string): What the user typed.timeZone(string, optional): IANA zone the text is local to (default UTC).from(string, optional): ISO time to count from (default now).
Returns string | null: The moment as an ISO 8601 UTC string, or null when the text isn't a time.
zonedTime(iso?, timeZone?): ZonedTime
The wall-clock time in a zone: year, month (1-12), day, hour, minute, second, weekday ("Monday"), offsetMinutes and a local ISO string. Scripts only get UTC Dates (there's no Intl), so this is how to ask "is it the weekend in New York?". Daylight saving is handled.
iso(string, optional): ISO time, Date or unix seconds (default now).timeZone(string, optional): IANA zone such as "America/New_York" (default UTC).
Returns ZonedTime: The local time parts.
fromZonedTime(parts, timeZone?): string
The opposite of zonedTime(): a local date and time in a zone as a UTC ISO string. A time skipped when the clocks go forward moves forward by the jump; a time repeated when they go back takes the earlier one.
parts({ year: number, month: number, day: number, hour?: number, minute?: number, second?: number } | string): The local time as parts, or as text like "2026-10-02 20:00".timeZone(string, optional): IANA zone (default UTC).
Returns string: ISO 8601 UTC string.
formatDate(iso, timeZone?, pattern?): string
Formats a time as text in a zone, in English, for places Discord's timestamp markup doesn't render (embed footers, channel names, KV keys). Tokens: yyyy, MMMM, MMM, MM, M, dddd, ddd, dd, d, HH, H, hh, h, mm, ss, tt (AM/PM), zzz (offset); text in 'single quotes' is kept as is. For messages, prefer formatTimestamp().
iso(string): ISO time, Date or unix seconds (default now).timeZone(string, optional): IANA zone (default UTC).pattern(string, optional): Format pattern (default "yyyy-MM-dd HH:mm").
Returns string: The formatted text.
nextOccurrence(cron, timeZone?, from?): string | null
The next time a five-field cron expression fires in a zone, e.g. nextOccurrence("0 20 * * 5", "Europe/London") for "next Friday 20:00 London time".
cron(string): minute hour day month weekday.timeZone(string, optional): IANA zone (default UTC).from(string, optional): ISO time to search after (default now).
Returns string | null: ISO 8601 UTC string, or null when it never fires again.
Data types
Shapes referenced by the functions above as parameters or return values. See the ctx object for ForumTag and every ctx.*-nested type.
AutoModRuleOptions
An AutoMod rule to create, or the parts of one to change.
- name?: string
- trigger?: "keyword" | "spam" | "preset" | "mentionSpam" | "memberProfile" (What the rule looks for (default "keyword"). Set when creating only.)
- event?: "messageSend" | "memberUpdate" (memberUpdate for memberProfile rules (names and bios); messageSend otherwise.)
- keywords?: string[] (Up to 1,000 of at most 60 characters; * is a wildcard ("bad*").)
- regex?: string[] (Up to 10 Rust-syntax patterns of at most 260 characters.)
- allow?: string[] (Words that never trigger the rule.)
- presets?: ("profanity" | "sexualContent" | "slurs")[] (Discord's own lists, for trigger "preset".)
- mentionLimit?: number (mentionSpam: unique mentions per message (1-50).)
- raidProtection?: boolean (mentionSpam: also catch mention raids.)
- actions?: { type: "block" | "alert" | "timeout" | "blockInteraction", message?: string, channelId?: string, seconds?: number }[] (block (with an optional 150-character message), alert (post to channelId), timeout (seconds, up to 4 weeks), blockInteraction. Default: block.)
- exemptRoles?: string[] (Up to 20 roles the rule ignores.)
- exemptChannels?: string[] (Up to 50 channels the rule ignores.)
- enabled?: boolean (Default true.)
- reason?: string (Audit log reason; the script's name is added.)
AutoModRule
An AutoMod rule as listAutoModRules() returns it.
- id: string
- name: string
- creatorId: string
- createdByBot: boolean (True for rules Mallard's scripts created.)
- trigger: string
- event: string
- enabled: boolean
- keywords: string[]
- regex: string[]
- allow: string[]
- presets: string[]
- mentionLimit: number | null
- raidProtection: boolean
- actions: { type: string, message: string, channelId: string, seconds: number | null }[]
- exemptRoles: string[]
- exemptChannels: string[]
FeedItem
One entry in a feed.
- id: string (The item's guid/id.)
- title: string
- link: string
- published: string (ISO 8601 (empty when the feed doesn't say).)
- updated: string (ISO 8601 (empty when the feed doesn't say).)
- author: string
- summary: string (Plain text with HTML removed, at most 2,000 characters.)
- imageUrl: string (An image from the item's enclosure or media tags (empty when none).)
- categories: string[]
ZonedTime
A moment as the wall clock shows it in one time zone (zonedTime()).
- year: number
- month: number (1-12.)
- day: number
- hour: number (0-23.)
- minute: number
- second: number
- weekday: string ("Monday" … "Sunday".)
- offsetMinutes: number (The zone's offset from UTC at that moment, e.g. 60 for BST.)
- iso: string (Local ISO 8601 with offset, e.g. "2026-10-02T20:00:00+01:00".)
- timeZone: string
CachedMessage
A message returned by getMessage()/getMessages() (fetched live over REST).
- id: string
- channelId: string
- authorId: string
- authorUsername: string
- authorDisplayName: string (Global display name, falling back to the username.)
- authorAvatarUrl: string
- content: string
- attachments: AttachmentContext[]
- referencedMessageId: string
- isForwarded: boolean
- forwardedAttachments: AttachmentContext[] (Uploads carried inside the forwarded snapshot of a forward.)
- 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
- type: string (Discord's message type (see MessageContext.type).)
- flags: string[]
- webhookId: string
- stickers: StickerSummary[]
- poll: PollData | null
- reactions: MessageReaction[]
- embeds: EmbedData[] (The message's embeds, in the same shape sendMessage()/editMessage() accept, so you can pass them straight back into editMessage() to keep an embed while changing only the buttons (or vice versa). Round-trips everything Mallard can write; only video embeds and the provider line are dropped.)
- buttons: ButtonData[] (The message's buttons, link buttons included. Buttons the bot itself attached round-trip with their handler; another bot's come back with a label only.)
- selects: SelectMenuData[] (The message's select menus, so a handler can re-send them with a new default or disabled state. Only menus Mallard sent carry their handler.)
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[]
- createdAt: string (ISO 8601 account creation time.)
RoleSummary
A cached guild role returned by listRoles(), and the role entries of ctx.resolved.
- id: string
- name: string
- colorArgb: number (0 means no colour.)
- position: number (Higher outranks lower.)
- hoist: boolean
- mentionable: boolean
- managed: boolean (A bot, integration or booster role: nobody can add or remove it, so skip these.)
- permissions: string[] (Permission names the role grants, e.g. "BanUsers".)
ChannelSummary
A cached guild channel returned by listChannels(), and the channel entries of ctx.resolved.
- id: string
- name: string
- type: string ("Text", "Voice", "Category", "Announcement", "Forum", "MediaForum", "Stage" or "Unknown" (empty until the bot next reconnects and records it).)
- parentId: string (The category it sits under (empty at top level).)
- position: number
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.)
- permissions: string[] (Permission names the role grants.)
- unicodeEmoji: string (The role icon when it is a unicode emoji (empty otherwise).)
ChannelInfo
One channel's detail, returned by getChannel(), discord.channels.get/fetch, ctx.guild.channels.fetch and ctx.client.channels.fetch.
- 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).)
- userLimit: number (Voice/stage user limit (0 = none or not a voice channel).)
- bitrate: number (Voice bitrate in bits/s (0 when not a voice channel).)
- send: (options: string | MessageOptions) => Promise<any> (Sends a message to this channel (Discord.js syntax).)
- delete: () => Promise<any> (Deletes this channel.)
- messages: ChannelMessagesManager (Message manager (.fetch, .fetchMany, .edit, .delete, .react).)
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.)
AiChatUsage
Token usage stats for an AI completion.
- promptTokens: number
- completionTokens: number
- totalTokens: number
AiChatResponse
Result of an aiChat() call.
- content: string (Generated assistant text response.)
- text: string (Convenient alias for content.)
- model: string (The model used to generate the response.)
- finishReason: string (Why the completion finished (e.g. "stop", "length").)
- usage?: AiChatUsage (Token counts consumed by this completion.)
AiChatMessage
A chat message in an AI conversation.
- role: string (Message role: "system", "user", or "assistant".)
- content: string (Message text content.)
AiChatOptions
Options for an aiChat() request.
- prompt?: string (Single user prompt (convenience shorthand for messages).)
- messages?: AiChatMessage[] (Conversation history messages.)
- model?: string (Model identifier. Defaults to the guild's configured default (google/gemini-3.5-flash-lite).)
- maxTokens?: number (Maximum tokens to generate (default: 4096).)
- temperature?: number (Sampling temperature 0.0-2.0 (default: 0.7).)
- reasoning?: object | string (Reasoning effort: { effort: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" } or "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max".)
- webFetch?: boolean (Fetch and extract content from URLs in the prompt (openrouter:web_fetch).)
- webSearch?: boolean (Search the web for real-time information (openrouter:web_search).)
- datetime?: boolean (Provide current date, time, and timezone to the model (openrouter:datetime).)
- imageGen?: boolean (Allow model to generate images from prompts (openrouter:image_generation).)
- advisor?: boolean (Allow model to consult a stronger model for guidance (openrouter:advisor).)
- subagent?: boolean (Allow model to delegate subtasks to a worker model (openrouter:subagent).)
- tools?: any[] (Array of tool specifications. Pass [] to disable all tools.)
- timeoutMs?: number (Request timeout in milliseconds (default: 25000, max: 28000).)
EmbedData
A rich embed. Every field is optional, so set only what you need.
- title?: string
- url?: string (Makes the title a link.)
- description?: string
- colorArgb?: number (Colour of the left-hand bar, as a decimal number (e.g. 0xED4245 / 15548997 for red).)
- authorName?: string
- authorIconUrl?: string
- authorUrl?: string (Makes the author line a link.)
- footerText?: string
- footerIconUrl?: string
- timestamp?: string (ISO-8601 timestamp, e.g. new Date().toISOString().)
- imageUrl?: string
- thumbnailUrl?: string (The small image in the top-right corner.)
- fields?: EmbedField[] (Up to 25 name/value pairs, e.g. a stats card. Inline fields sit up to three to a row.)
EmbedField
One name/value pair on an embed.
- name: string (Up to 256 characters.)
- value: string (Up to 1024 characters.)
- inline?: boolean (Share a row with neighbouring inline fields.)
ButtonData
A button attached to a message: a handler button that runs a script, or a link button (url).
- label: string
- style?: "primary" | "secondary" | "success" | "danger" | "link" (A button with a url is always a link button, whatever this says.)
- 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.)
- url?: string (Makes it a link button that opens this URL instead of running a script (no handler or payload).)
- emoji?: string (A unicode emoji ("🦆") or a custom one as "name:id", shown before the label. A label may be empty when there is an emoji.)
- disabled?: boolean (Greys the button out so it can't be clicked. Defaults to false. Common pattern for a one-time action: the click handler re-sends the same buttons with this one set true, so only the first click goes through.)
SelectMenuData
A select menu attached to a message. Each menu takes a whole row, and a message holds at most 5 rows in total (buttons fill rows five at a time).
- handler: string (Name of the "Select Menu" (or "Select Menu: Modal") script that runs when someone picks.)
- type?: "string" | "user" | "role" | "channel" | "mentionable" (What the menu lists. "string" (the default) shows your options; the others let the user pick server members, roles or channels.)
- options?: SelectOption[] (The choices of a string menu (1-25).)
- payload?: string (Passed as ctx.interaction.payload (max 50 chars).)
- placeholder?: string (Grey text shown before anything is picked.)
- minValues?: number (Fewest picks allowed (default 1; 0 lets them clear it).)
- maxValues?: number (Most picks allowed (default 1, up to 25).)
- channelTypes?: string[] (Channel menus only: limit to kinds such as "text", "voice", "forum", "category".)
- defaultValues?: string[] (User/role/channel menus only: IDs pre-selected when the menu shows.)
- disabled?: boolean
SelectOption
One choice in a string select menu.
- label: string
- value: string (What ctx.interaction.selected will contain.)
- description?: string
- emoji?: string (Unicode emoji or "name:id".)
- default?: boolean (Pre-selected.)
MessageOptions
Everything a message can carry, as one object. Every send function takes this in place of its content argument (sendMessage(channelId, { content, embeds, selects, silent })), which is the only way to attach select menus, files, stickers or flags.
- content?: string (Up to 2000 characters.)
- embeds?: EmbedData[] | EmbedData (Up to 10.)
- buttons?: ButtonData[] (Up to 25, five to a row.)
- selects?: SelectMenuData[] (Each menu takes its own row.)
- pings?: boolean (Whether mentions notify (default true).)
- replyTo?: string (Message ID in the same channel to reply to (sendMessage only).)
- silent?: boolean (Deliver without a notification sound, like typing @silent. Unlike pings:false, mentions still highlight.)
- suppressEmbeds?: boolean (Hide link previews.)
- files?: FileData[] (Attachments, up to 10.)
- stickerIds?: string[] (Up to 3 of the server's stickers (see getStickers()).)
- components?: any[] (Standard Discord message components (ActionRows, buttons, select menus).)
- ephemeral?: boolean (followUp() only: show it to the invoking user alone.)
- layout?: LayoutBlock[] (A Components V2 layout: containers with an accent colour, sections with a thumbnail or button beside the text, image galleries, separators, button rows and menus. Can't be combined with content, embeds, buttons, selects or stickers, and a message sent with a layout keeps it for good (an edit can't turn it back into a plain message). Up to 40 components and 4,000 characters of text. Works with sendMessage, sendDm, editMessage, editResponse and followUp, not webhooks.)
LayoutBlock
One block of a layout. type picks the rest: "container" (children, accentColor, spoiler), "text" (text, markdown allowed), "section" (text: 1-3 strings, accessory), "separator" (divider, spacing), "gallery" (items), "buttons" (1-5 buttons) or "select" (select). Containers hold every type but another container.
- type: "container" | "text" | "section" | "separator" | "gallery" | "buttons" | "select"
- text?: string | string[] (text: the markdown. section: 1-3 strings shown stacked.)
- children?: LayoutBlock[] (container: the blocks inside.)
- accentColor?: number (container: the bar colour, e.g. 0x5865F2.)
- spoiler?: boolean (container: blur it until clicked.)
- accessory?: { thumbnail?: string, description?: string, spoiler?: boolean, button?: ButtonData } (section: an image URL (or attachment://name) or one button beside the text.)
- divider?: boolean (separator: draw a line (default true).)
- spacing?: "small" | "large" (separator: gap size (default small).)
- items?: { url: string, description?: string, spoiler?: boolean }[] (gallery: 1-10 images (https:// or attachment://name).)
- buttons?: ButtonData[] (buttons: a row of 1-5.)
- select?: SelectMenuData (select: one menu.)
FileData
A file to attach. Give either content (text you build in the script, e.g. a transcript or CSV) or mediaId (an image from the server's Media page).
- name: string (File name with extension, e.g. "transcript.txt".)
- content?: string (Text content, sent as UTF-8 (up to 1 MB).)
- mediaId?: string (ID of an image on the server's Media page.)
- spoiler?: boolean (Blur it until clicked.)
- description?: string (Alt text.)
StickerSummary
A sticker, on a message or from getStickers().
- id: string
- name: string
- format: string ("Png", "Apng", "Lottie" or "Gif".)
- description: string (getStickers() only.)
- available: boolean (getStickers() only: false when over the boost limit.)
PollData
A native poll, on a message or from getPollResults().
- question: string
- answers: PollAnswer[]
- allowMultiselect: boolean
- expiresAt: string (ISO 8601 (empty when unknown).)
- isFinalized: boolean (True once the poll has closed and the counts are exact.)
- totalVotes: number
PollAnswer
One answer of a poll, with its running count.
- id: number (The answerId that votes and getPollVoters() use.)
- text: string
- emoji: string (Unicode emoji or "name:id" (empty when none).)
- count: number
- meVoted: boolean
BanOptions
Options passed when banning a user or member.
- reason?: string (Audit log reason for the ban.)
- delete_message_seconds?: number (Number of seconds of message history to delete (0 to 604800, max 7 days).)
RoleCreateOptions
Options for creating a role in the guild.
- name?: string (Role name (up to 100 characters).)
- color?: number (RGB color integer (e.g. 0xff0000).)
- hoist?: boolean (Whether the role is displayed separately in the sidebar.)
- mentionable?: boolean (Whether anyone can mention this role.)
- permissions?: string[] | string | number | bigint (Permissions granted by the role.)
- reason?: string (Audit log reason for role creation.)
UserSummary
A Discord user, member of this server or not (getUser(), ctx.resolved.users).
- userId: string
- username: string
- displayName: string (Global display name, falling back to the username.)
- avatarUrl: string (Never empty.)
- bannerUrl: string (Empty when unset or unknown.)
- isBot: boolean
- createdAt: string (ISO 8601 account creation time.)
- primaryGuildTag: string (The server tag they display (empty when none).)
AuditLogEntry
One audit-log entry (ctx.auditLog, getAuditLog()).
- id: string
- actionType: string (NetCord's name for the action, e.g. "GuildUserBanAdd", "ChannelDelete", "RoleUpdate".)
- userId: string (Who did it (empty when Discord doesn't say).)
- targetId: string (What it was done to (a user, channel, role… per actionType).)
- reason: string
- changes: { key: string, oldValue: any, newValue: any }[] (What changed; key is Discord's snake_case field name.)
- options: Record<string, any> | null (Action-specific extras, e.g. channelId, count, roleName.)
- createdAt: string (ISO 8601.)
SearchResult
Result of searchMessages().
- total: number (Matches Discord found in all (more than returned).)
- messages: CachedMessage[] (Newest first unless sorted by relevance.)
BulkResult
Result of a bulk operation: which IDs it worked for and which it didn't.
- succeeded: string[]
- failed: string[]
VoiceMember
Someone in a voice channel (getVoiceMembers(), getVoiceState()).
- userId: string
- channelId: string
- selfMuted: boolean
- selfDeafened: boolean
- serverMuted: boolean
- serverDeafened: boolean
- streaming: boolean
- video: boolean
- suppressed: boolean
- joinedAt: string (ISO 8601 time Mallard saw them join this channel (empty when unknown).)
PermissionOverwrite
One permission overwrite on a channel (getChannelPermissions()).
- targetId: string (Role or member ID; the server's own ID is @everyone.)
- type: string ("role" or "member".)
- allow: string[]
- deny: string[]
ThreadSummary
An active thread (listActiveThreads()).
- id: string
- name: string
- parentId: string
- ownerId: string
- archived: boolean
- locked: boolean
- messageCount: number
- memberCount: number
- createdAt: string (ISO 8601.)
- lastMessageId: string (Compare with snowflakeTime() to find idle threads.)
KvEntry
A key with its value (listKVEntries()).
- key: string
- value: string (The stored JSON string.)
- expiresAt: string (ISO 8601 expiry (empty when it never expires).)
PendingJob
A runLater() job still waiting (listLater()).
- jobId: string
- scriptName: string
- dueAt: string (ISO 8601.)
- scheduledBy: string
IntegrationSummary
An integration installed on the server (getIntegrations()).
- id: string
- name: string
- type: string ("twitch", "youtube", "discord" (a bot) or "guild_subscription".)
- enabled: boolean
- accountName: string
ChannelOptions
Settings for createChannel()/modifyChannel(). Every field is optional.
- name?: string
- topic?: string
- parentId?: string (Category to move it under (empty string: top level).)
- position?: number
- nsfw?: boolean
- slowmode?: number (Seconds (0-21600).)
- userLimit?: number (Voice/stage only (0 = none, up to 99).)
- bitrate?: number (Voice only, in bits/s (8000-96000, more with boosts).)
- autoArchiveMinutes?: number (Threads: 60, 1440, 4320 or 10080.)
- syncPermissions?: boolean (modifyChannel: reset its permissions to match its category.)
- overwrites?: { targetId: string, targetIsRole?: boolean, allow?: string[], deny?: string[] }[] (Permission overwrites. On createChannel they are the channel's whole set; on modifyChannel they replace it.)
- reason?: string (Audit log reason.)
SearchQuery
What searchMessages() looks for. Combine as many as you like.
- content?: string (Words in the message.)
- authorId?: string
- channelId?: string
- mentions?: string (A user ID the message mentions.)
- has?: string ("link", "file", "image", "video", "embed", "poll" or "sticker".)
- pinned?: boolean
- sortBy?: string ("timestamp" (default, newest first) or "relevance".)
- limit?: number (1-25 (default 25).)
- offset?: number (Skip this many results, to page.)
PurgeOptions
What purge() deletes.
- count: number (Most messages to delete (1-100).)
- authorId?: string (Only this user's messages.)
- contains?: string (Only messages containing this text (case-insensitive).)
- bots?: boolean (Only messages from bots and webhooks.)
- attachments?: boolean (Only messages with files.)
- before?: string (Start scanning before this message ID.)
- reason?: string (Audit log reason.)