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