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 and ctx.fetchCallsRemaining.

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.
no ctx.channelId
GuildMemberRemove MemberContext A member leaves or is removed from the guild.
no ctx.channelId
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.
no ctx.channelId
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.
no ctx.channelId
GuildBanRemove BanContext A user is unbanned (has no other trigger).
no ctx.channelId
GuildAuditLogEntryCreate eventJson (JSON string) A new audit log entry is created. The entry is in ctx.eventJson, including its changes array (what actually changed, before and after) and the action-specific options; no typed DTO, since audit log entries vary widely by action type. The fields are listed under Context.
no ctx.channelId
AutoModerationActionExecution eventJson (JSON string) Discord's AutoMod takes an action. The payload is in ctx.eventJson, whose fields are listed under Context; note they are Mallard's camelCase names, not Discord's raw snake_case ones.
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.actorIdno ctx.channelId
GuildScheduledEventUpdate ScheduledEventContext A scheduled event changes (time, status, details).
no ctx.actorIdno ctx.channelId
GuildScheduledEventDelete ScheduledEventContext A scheduled event is deleted.
no ctx.actorIdno 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
Forms(1)

Dispatched when a user submits a public guild form at /forms/{guildId}/{formId}. Submissions are stored in the key-value store automatically, and the script receives the answers in ctx.form.

Trigger Populates Description & notes
FormSubmitted FormContext A user submits a public guild form at /forms/{guildId}/{formId}. Fires for every form on the guild, so branch on ctx.form.formName (or formId) when you only want one. Not dispatched by a gateway event: no ctx.channelId/ctx.actorId at all, since the submitter may be anonymous or identified via Twitch.
no ctx.actorIdno ctx.channelId
Submissions are stored in the key-value entry forms:submissions:<formId> whether or not a script is listening, so disabling every FormSubmitted script stops the notifications, not the form. Use the form's own Enabled switch on the Forms tab for that.

Filters

A trigger says what kind of event runs a script. Filters, set on the script in the dashboard, say which of those events are worth running it for: only #support, only messages starting with !ticket, only the ⭐ reaction, only bans.

They are applied before the script is queued, so an event that doesn't match costs no executions, no runtime and no REST actions against your hourly limits, and never reaches the script at all, so there is nothing for it in the logs either. The editor shows only the filters the selected trigger can actually answer.

Filter Available on What it does
Channels Messages, reactions, threads, polls, voice, stage, invites, AutoMod, pins, webhooks, and every command or button Only these channels, or every channel except these. A message posted in a thread also matches the channel the thread hangs under, so naming a forum covers every post in it. Archived threads aren't in the gateway cache, so only their own ID matches.
Message content MessageCreate, MessageUpdate, MessageDelete, DirectMessageCreate, AutoModerationActionExecution Run only when the message contains one of a list of phrases, or matches a regular expression, or only when it doesn't. Matched against the new text on an edit. Patterns run on a linear-time engine, so lookarounds, backreferences and atomic groups are rejected when you save; use "doesn't match" instead of a negative lookahead.
Member roles Commands and buttons, member joins/leaves/updates, reactions, messages, voice Only members holding one of these roles, or skip them. On a member update this reads the roles they hold after the change. A member Mallard hasn't cached yet is never filtered out by this; the filter is skipped rather than treated as "no roles".
Specific members Every trigger with a ctx.actorId Only these members, or ignore them. Handy for muting one noisy integration.
Reaction emoji The four reaction triggers Only these emoji, or every emoji except these. Give the emoji itself, or a custom emoji's name or ID. Clearing all reactions from a message carries no emoji, so the filter doesn't apply there.
Audit log actions GuildAuditLogEntryCreate Only these audit actions (MemberBanAdd, ChannelDelete, …). Without it this trigger fires for every administrative action in the server, so it's usually the filter that saves the most runs.
Attachments MessageCreate, MessageUpdate, MessageDelete, DirectMessageCreate Only messages that have an attachment, or only ones that don't. On a delete this reads the cached copy, so a message older than 30 minutes has neither and the filter doesn't apply.
Other bots and webhooks MessageCreate, MessageUpdate, MessageDelete Off by default: messages from other bots and webhook posts never run a script unless you turn this on. Mallard's own messages never do either way, since that would be a script triggering itself.
Cooldown Every trigger except Scheduled, which already has an interval A minimum number of seconds between runs, either for the script as a whole or for each member separately. A command used during the cooldown gets a private "on cooldown" reply; an event that arrives during it is discarded rather than queued, so the script just doesn't run for it.
An unhandled error has occurred. Reload 🗙