brain/wiki/flows-execution/triggers.md
Triggers define how and when a flow starts. The module handles registration, event capture, testing, and deduplication, tracking each enabled trigger as a TriggerSource record and driving enable/disable side effects (BullMQ scheduling, external webhook registration).
POLLING, WEBHOOK, APP_WEBHOOK, MANUAL.(projectId, flowId, simulate).sourceName format: pieceName@version:triggerName.(appName, event, identifierValue) to a flow.flow-trigger-side-effect.ts, trigger-source-service.ts, dedupe-service.ts, test-trigger-service.ts.setSchedule supplies either a cron (CRON_EXPRESSION) or a rolling interval (INTERVAL → BullMQ every); when the piece sets nothing the default is a rolling interval of AP_TRIGGER_DEFAULT_POLL_INTERVAL minutes (default 5). WEBHOOK submits ON_ENABLE hook (+ renewal job if the piece needs periodic re-registration); APP_WEBHOOK creates routing records.testTriggerService, distributed-locked): SIMULATION creates a simulate=true source and collects events; TEST_FUNCTION submits a TEST hook and saves outputs as TriggerEvents.__DEDUPE_KEY_PROPERTY, Redis INCR with 30s TTL — first passes, duplicates filtered; the dedupe key is stripped from returned payloads.isRepublish): republishing a running flow does onDisable(old) → onEnable(new), which used to reset lastPoll/lastItem to now and silently drop events created in between. flowService.update sets isRepublish=true only for a LOCK_AND_PUBLISH of an already-ENABLED flow whose trigger is unchanged — same piece, same trigger name, and deep-equal settings.input (flowPublishUtils.isSameTrigger); the flag is threaded through the ON_ENABLE job → ExecuteTriggerOperation → trigger context (context.isRepublish), and pollingHelper.onEnable then keeps the existing checkpoint. A fresh enable, a manual off→on toggle, a trigger swap, and any change to the trigger's props all still reset to now. The props check is not cosmetic: a checkpoint kept across a props change points at a resource that is no longer being polled, and pollingHelper.poll treats a LAST_ITEM id it cannot find in the fetched page (findIndex → -1) the same as "no checkpoint", emitting every item. Custom polling triggers that don't use pollingHelper can opt in by reading context.isRepublish.*/X cron is not "every X minutes" — it means "minutes divisible by X", so it double-fires at :00 and :X for X > 30 and gaps unevenly when X doesn't divide 60. Use INTERVAL/intervalMs for a rolling interval; reserve cron for wall-clock schedules. This bit the default poll schedule until GIT-1632.triggerRunStats): Redis key trigger_run:{platformId}:{pieceName}:{date}:{status}, 14-day retention, shown in Platform Admin (Cloud).All four strategies available in CE/EE/Cloud. Cloud additionally surfaces trigger health stats in Platform Admin.
Entry point: flowTriggerSideEffect, exported from trigger-source/flow-trigger-side-effect.ts and called by trigger-source-service.ts on enable and disable.
packages/server/api/src/app/trigger/trigger-source/ — TriggerSource CRUD, entity, and the enable/disable side effects per strategypackages/server/api/src/app/trigger/trigger-events/ — TriggerEvent storage, entity, and endpointspackages/server/api/src/app/trigger/test-trigger/ — simulation and test-function modes, plus their endpointspackages/server/api/src/app/trigger/app-event-routing/ — APP_WEBHOOK routing table and entitypackages/server/api/src/app/trigger/trigger-run/ — per-platform trigger health tracking and stats endpointspackages/server/api/src/app/trigger/dedupe-service.ts — Redis-based deduplication for pollingpackages/server/api/src/app/trigger/trigger.module.ts — module registrationpackages/core/shared/src/lib/automation/trigger/ — TriggerSource schema, TriggerStrategy enum, handshake and schedule optionspackages/web/src/app/builder/test-step/ — builder test panel, event selector, and the manual webhook test dialogpackages/web/src/app/builder/flow-canvas/ — trigger node widget and the add-trigger button above itPaths verified 2026-07-17.