Skip to content

Recipe - workflow with a cron trigger ​

Goal: a workflow that runs every day at 02:30 UTC. Fields and rules are in the Workflow reference and the Trigger reference.

Three things must all be on before anything runs: the trigger is active, it has a workflow, and the workflow is on in the environment (devActive for development, on by default; active for production, off by default). Create them in this order so the schedule never starts a half-built workflow.

1. Create the workflow ​

Call save_workflow:

json
{
  "projectId": "<project id>",
  "name": "Nightly report",
  "description": "Builds the daily report",
  "timeoutSeconds": 600
}

It answers { "id": "<workflow id>" }. The default timeout is 300 seconds. The most you can set is 3600.

2. Build its canvas ​

Call get_canvas:

json
{ "target": { "kind": "workflow", "id": "<workflow id>" } }

A new workflow has an entrypoint and an error_handler and no edges. Note the entrypoint id and the revision.

Call edit_canvas:

json
{
  "target": { "kind": "workflow", "id": "<workflow id>" },
  "revision": 0,
  "validate": true,
  "ops": [
    { "op": "add_block", "ref": "report", "type": "jsrunner",
      "data": { "value": "return { ranAt: new Date().toISOString() };" },
      "connect_from": { "from": "entrypoint_1" } },
    { "op": "add_block", "ref": "log", "type": "consolelog",
      "data": { "message": "Nightly report ran", "level": "info" },
      "connect_from": { "from": "report" } }
  ]
}

A workflow has no caller, so it needs no response block. For the shape of other blocks use get_block_schemas with blockTypes.

3. Switch the workflow on ​

Call save_workflow:

json
{ "workflowId": "<workflow id>", "active": true }

A workflow that is off in an environment is not given to that environment's workers, so no trigger can start it there. This step switches production on; development is on already.

4. Create the schedule trigger ​

Call save_trigger:

json
{
  "projectId": "<project id>",
  "name": "Nightly report at 02:30",
  "type": "schedule",
  "schedule": "0 30 2 * * *",
  "timezone": "UTC",
  "workflowId": "<workflow id>",
  "active": true
}

It answers { "id": "<trigger id>", "warnings": [] }.

The schedule has six fields: seconds, minutes, hours, day of month, month, day of week. 0 30 2 * * * is second 0, minute 30, hour 2, every day. Five fields are refused.

WantWrite
Every 15 minutes0 */15 * * * * or @every 15m
Weekdays at 09:000 0 9 * * 1-5
Every day at midnight0 0 0 * * *
Once@at 2026-12-31T23:00:00Z

Runs are spread over the minute unless you name the second. @daily can run at 00:00:37. Use UTC for a run that must happen exactly once a day, since a timezone with daylight saving can skip or repeat a run twice a year.

5. Check it ​

Call list_triggers:

json
{ "projectId": "<project id>", "workflowId": "<workflow id>" }

Expect active: true, the workflow listed, and disabledReason: null.

6. Watch the first run ​

To avoid waiting until 02:30, point the trigger at a short interval and record the runs:

Call save_workflow:

json
{ "workflowId": "<workflow id>", "recordExecution": true }

Call save_trigger:

json
{ "triggerId": "<trigger id>", "schedule": "@every 1m" }

After two minutes:

Call list_recordings:

json
{ "projectId": "<project id>", "kind": "workflow", "targetId": "<workflow id>" }

Read a run with get_recording (see Recipe, debug and fix a route or workflow). When it works, put the real schedule back and turn recording off:

Call save_trigger:

json
{ "triggerId": "<trigger id>", "schedule": "0 30 2 * * *" }

Call save_workflow:

json
{ "workflowId": "<workflow id>", "recordExecution": false }

7. Test the workflow itself ​

A test suite runs the workflow directly, with an input you choose. The schedule is not used. See Recipe, write and run a test suite.

Good to know ​

  • A schedule is one run each time, never a batch. Set payload on the trigger to hand every run the same input. Otherwise the run has none.
  • A run that fails is retried a few times. Make the work safe to run twice.
  • After downtime, missed runs are dropped, except @at.
  • To pause the schedule use save_trigger with active: false. Turning it on again resumes from the next scheduled time.
  • delete_trigger removes the schedule. The workflow stays.
  • Two workflows on the same clock need two triggers.

Common problems ​

What you seeWhat to do
Invalid input: schedule: A cron expression needs six fields...Add the leading seconds field.
Invalid input: timezone: Use an IANA timezone name...Use a name such as Asia/Kolkata, not +05:30.
Fluxify API error 409: trigger with that name already existsPick another trigger name.
Nothing runsTrigger off, no workflow attached, or the workflow is switched off in that environment. Check list_triggers and get_workflow.
disabledReason is setThe system switched the trigger off. Fix the cause, then save with active: true.
Runs fail at onceLook at get_system_logs for compile errors, then at the recording.

Released under the Apache License 2.0. Enterprise features are under the Fluxify Enterprise Edition License.