Mallard

Triggers

Every script runs on exactly one of these 51 triggers, set when the script is created. Each entry below names the ctx.* object that trigger populates; see the ctx object for every field on every one of those types.

Always populated

Every trigger, with no exceptions, populates ctx.guildId, ctx.eventType, ctx.args, ctx.env, ctx.secret, ctx.fetchCallsRemaining and ctx.restActionsRemaining.

ctx.actorId and ctx.channelId are populated on almost every trigger too: the tables below flag the specific triggers where one or both genuinely don't exist (e.g. there's no "channel" for a server-settings change, and no "actor" for a Twitch event), rather than being empty/zero placeholders.

Commands & interactions(8)

Answered by the trigger type, not the script: plain command/button triggers get a deferred "thinking…" ack that the script fills in with editResponse(); the four ": Modal" variants are answered with the form in the script's own config instead, and the script body runs on submit. See Commands & modals for the full picture.

Trigger Populates Description & notes
SlashCommand none A user runs a /command. The bot replies with a message the script fills in via editResponse().
SlashCommand: Modal InteractionContext A user runs a /command and a popup form opens immediately. The script body runs when the form is submitted, with the answers in ctx.interaction.values and the command's options still in ctx.args.
MessageCommand MessageContext A user runs a message context-menu command (right-click a message → Apps). The target message is in ctx.message. Names may include spaces and capitals, and are matched verbatim (not lowercased).
MessageCommand: Modal MessageContext + InteractionContext Same, but a popup form opens immediately; the script runs on submit with ctx.message still available.
UserCommand MemberContext A user runs a user context-menu command (right-click a user → Apps). The target member is in ctx.member (ctx.actorId is the invoker). Names may include spaces and capitals, matched verbatim.
UserCommand: Modal MemberContext + InteractionContext Same, but a popup form opens immediately; the script runs on submit with ctx.member still available.
ButtonClick InteractionContext A user clicks a button a script sent. ctx.interaction.payload carries the button's payload (set by the sending script), and ctx.interaction.customId/messageId/channelId identify where it was clicked.
ButtonClick: Modal InteractionContext A user clicks a button a script sent and a popup form opens immediately; the script runs on submit, with the button's payload in ctx.interaction.payload alongside the form's ctx.interaction.values.
Members(3)
Trigger Populates Description & notes
GuildMemberAdd MemberContext A member joins the guild.
GuildMemberRemove MemberContext A member leaves or is removed from the guild.Not dispatched when the removed member is the bot itself (Discord sends this right before the guild becomes inaccessible on kick/ban).
GuildMemberUpdate MemberContext A member's roles, nickname, or other profile fields change.
Messages(4)
Trigger Populates Description & notes
MessageCreate MessageContext A message is posted in a guild channel.
MessageUpdate MessageContext A message is edited. content is the new text; previousContent is what it said before, recovered from the 30-minute message cache (null when unknown, e.g. after a restart).Only fires for an actual content edit. Discord also resends this event for embed population and other non-edit changes, which are filtered out before dispatch.
MessageDelete MessageContext A message is deleted. content/authorId are recovered best-effort from the 30-minute message cache: empty strings on a cache miss (message never seen, aged out, or a bot restart happened since).
no ctx.actorId
MessageBulkDelete BulkDeleteContext Multiple messages are deleted at once.
no ctx.actorId
Reactions(4)
Trigger Populates Description & notes
MessageReactionAdd ReactionContext A reaction is added to a message.
MessageReactionRemove ReactionContext A reaction is removed from a message.
MessageReactionRemoveAll ReactionContext Every reaction is cleared from a message (no single user; ctx.reaction has no userId/emoji).
no ctx.actorId
MessageReactionRemoveEmoji ReactionContext Every reaction for one specific emoji is cleared from a message (ctx.reaction has the emoji but no single user).
no ctx.actorId
Polls(2)
Trigger Populates Description & notes
MessagePollVoteAdd PollVoteContext A user votes on a native Discord poll (ctx.pollVote.answerId is the chosen answer).
MessagePollVoteRemove PollVoteContext A user retracts a native Discord poll vote.
Threads & forums(4)
Trigger Populates Description & notes
ThreadCreate ThreadContext A thread or forum/media post is created. ctx.thread.isForumPost distinguishes a forum post from an ordinary text-channel thread, and ctx.channelId is the new thread itself.Only fires on genuine creation. Discord also resends this event when the bot merely regains visibility of an existing thread (e.g. on reconnect), which is ignored so old posts don't re-trigger scripts.
ThreadUpdate ThreadContext A thread or forum post is renamed, archived, locked, or re-tagged (ctx.thread reflects the current state, including applied forum tags).
ThreadDelete ThreadContext A thread or forum post is deleted. ctx.thread is degraded to only id/parentId/parentType/isForumPost: name, ownerId and tags are unavailable because the thread is already gone by dispatch time.
no ctx.actorId
ChannelPinsUpdate none A message is pinned or unpinned in a channel (ctx.channelId is the channel).
no ctx.actorId
Moderation events(4)
Trigger Populates Description & notes
GuildBanAdd BanContext A user is banned. Fires distinctly from GuildMemberRemove; who performed it is in the audit log, not ctx.ban.
GuildBanRemove BanContext A user is unbanned (has no other trigger).
GuildAuditLogEntryCreate eventJson (raw JSON string) A new audit log entry is created. The raw entry is in ctx.eventJson; no typed DTO, since audit log entries vary widely by action type.
no ctx.actorIdno ctx.channelId
AutoModerationActionExecution eventJson (raw JSON string) Discord's AutoMod takes an action. The raw payload is in ctx.eventJson.
Invites(2)
Trigger Populates Description & notes
InviteCreate InviteContext An invite is created (ctx.invite has the code, inviter, uses and expiry).
InviteDelete InviteContext An invite is deleted or expires (ctx.invite has only the code + channelId; Discord sends nothing else on delete).
no ctx.actorId
Scheduled events (Discord's native events)(5)

Discord's own "Scheduled Events" feature (an RSVP-able calendar entry on the server), distinct from the Scheduled trigger below, which is Mallard's own timer.

Trigger Populates Description & notes
GuildScheduledEventCreate ScheduledEventContext A scheduled event is created (ctx.scheduledEvent has the full event).
no ctx.channelId
GuildScheduledEventUpdate ScheduledEventContext A scheduled event changes (time, status, details).
no ctx.channelId
GuildScheduledEventDelete ScheduledEventContext A scheduled event is deleted.
no ctx.channelId
GuildScheduledEventUserAdd ScheduledEventContext (sparse) A user RSVPs to a scheduled event. ctx.scheduledEvent carries only id + userId (the subscribing user, also mirrored as ctx.actorId); no name/description/times.
no ctx.channelId
GuildScheduledEventUserRemove ScheduledEventContext (sparse) A user withdraws their RSVP from a scheduled event.
no ctx.channelId
Stage instances(3)
Trigger Populates Description & notes
StageInstanceCreate StageContext A stage channel goes live (ctx.stage has the channel + topic).
no ctx.actorId
StageInstanceUpdate StageContext A live stage changes its topic or privacy level.
no ctx.actorId
StageInstanceDelete StageContext A stage ends.
no ctx.actorId
Server settings(4)
Trigger Populates Description & notes
GuildUpdate GuildUpdateContext Server settings change (ctx.guildUpdate has name, description, ownerId).
no ctx.channelId
GuildEmojisUpdate ExpressionSetContext The server's custom emoji set changes (ctx.expressions.count / names).
no ctx.actorIdno ctx.channelId
GuildStickersUpdate ExpressionSetContext The server's sticker set changes (ctx.expressions.count / names).
no ctx.actorIdno ctx.channelId
WebhooksUpdate none A channel's webhooks change (ctx.channelId is the channel).
no ctx.actorId
Voice(1)
Trigger Populates Description & notes
VoiceStateUpdate VoiceStateContext A user joins, leaves, or moves between voice channels. ctx.voiceState.channelId is empty when the user disconnected.
Direct messages(1)
Trigger Populates Description & notes
DirectMessageCreate MessageContext The bot receives a DM. Not guild-scoped: it's fanned out to every guild the sender shares with an enabled script, using the live member cache. Zero matches gets a canned reply, one match dispatches directly, and multiple matches show a server-picker whose buttons route back through the same handling.
Timer(1)
Trigger Populates Description & notes
Scheduled none Runs on a timer instead of a Discord event; the basis for delayed or periodic work (temp-ban sweeps, reminders, feed polling).
no ctx.actorIdno ctx.channelId
The minimum interval is tier-gated (30/15/10/5 minutes on Free/Plus/Pro/Ultra), enforced both at save time and again at dispatch time, so a downgraded guild's existing scripts still get clamped.
Twitch(4)

Requires linking a Twitch channel on the guild's General page. Dispatched by a separate EventSub subsystem, not the Discord gateway.

Trigger Populates Description & notes
TwitchStreamOnline TwitchContext A linked Twitch channel goes live.
no ctx.actorIdno ctx.channelId
TwitchStreamOffline TwitchContext A linked Twitch channel goes offline.
no ctx.actorIdno ctx.channelId
TwitchChannelUpdate TwitchContext A linked Twitch channel changes its title or category.
no ctx.actorIdno ctx.channelId
TwitchChatBatch TwitchContext (messages populated) Buffered Twitch chat messages (and channel-point redemptions/bit cheers) for a linked channel, flushed every ~10 minutes, never per message, so it can't burn through the hourly execution budget. Additionally requires a chat-reader authorization from the streamer or one of their moderators.
no ctx.actorIdno ctx.channelId
Ban appeals(1)
Trigger Populates Description & notes
AppealSubmitted AppealContext A banned user submits the public ban-appeal form at /appeal/{guildId}. Not dispatched by a gateway event: no ctx.channelId/ctx.actorId at all, since the submitter may be identified via Twitch rather than Discord.
no ctx.actorIdno ctx.channelId
Shown to banned users only when the guild has at least one enabled AppealSubmitted script.
An unhandled error has occurred. Reload 🗙