Triggers & scheduling

The four trigger types, cron scheduling with collision policies, and input validation.

Every workflow declares at least one trigger in triggers: [...]. A trigger is how a run starts.

BuilderSignatureStarts a run via
apiTriggerapiTrigger(name, opts?)flowctl run start --workflow <name>
formTriggerformTrigger(name, opts?)submitting a trigger form
webhookTriggerwebhookTrigger(name, opts?)an inbound HTTP call
scheduleTriggerscheduleTrigger(name, { cron, collision?, timezone? })a cron schedule
triggers: [
  apiTrigger("create-order"),
  scheduleTrigger("nightly", { cron: "0 9 * * *", collision: "skip", timezone: "UTC" }),
]

Scheduling with scheduleTrigger

  • cron — a 5-field standard cron expression (e.g. "0 9 * * *" = 09:00 daily). The scheduler is minute-granular — no sub-minute schedules.
  • collision — what to do when a prior scheduled run is still active: "skip" (default), "queue" (start after the prior finishes), or "parallel" (start anyway).
  • timezone — an IANA name (e.g. "America/New_York"); defaults to the engine’s local zone.
Two scheduling gotchas

Keep the interval ≥ the run’s wall-clock duration. Under the default skip, a fire that lands while the prior run is still active is skipped by design, so a slow workflow runs less often than the cron implies.



A human-in-the-loop step makes the duration unbounded. A parked run stays “active”, so under skip it blocks every later fire until someone responds. Decouple the human leg (do the automated part and finish, hand off the HITL to a separate run), or use collision: “parallel” / “queue”.

Keep an apiTrigger for testing

Add an apiTrigger alongside a schedule so you can start the workflow on demand with flowctl run start while developing.

Validating start input

inputSchema on workflow({}) is a JSON Schema that validates the trigger --input at start time. A start that fails validation is rejected before any step runs.

workflow({
  name: "CreateOrder",
  inputSchema: {
    type: "object",
    properties: { order_id: { type: "string" }, total: { type: "number" } },
    required: ["order_id"],
  },
  initial: { order_id: null, total: 0 },   // --input is merged over this
  triggers: [ apiTrigger("create-order") ],
  steps: [ /* … */ ],
});
flowctl run start --workflow CreateOrder --input '{"order_id":"A-1","total":250}'
flowctl trigger schema api CreateOrder create-order -o json   # fetch the expected input shape

The start --input is merged over initial, so anything omitted falls back to its initial value.

Next: Errors, retries & recovery.