Triggers
A trigger is a cron schedule attached to an application. When it is due, the Manager sends an HTTP POST to every backend that has registered for that application. Use triggers for anything your backend must do on a timer (nightly reports, hourly syncs, cleanup jobs) without running a scheduler of your own. The Trigger System page covers the executor, the callback contract and the GraphQL API.
What you need first
Triggers only do something once a backend is listening:
- A machine user for the application's environment; see Machine Users.
- The backend registers with
POST /api/machine/register-backendand keeps sending heartbeats; see Backend Registration.
A trigger with no active backend is not lost: the Manager stores the occurrence and retries delivery with backoff until a backend registers or the retry budget runs out.
Where triggers live
Triggers belong to an application. Open a project, then the application, then its Triggers page (/<project-slug>/applications/<application-slug>/triggers). Any project member can view the page; creating, editing and deleting a trigger all need the maintainer or owner project role, or the system administrator role.
Creating a trigger
- Click Add Trigger.
- Fill in the form:
- Name (required, up to 100 characters)
- Description (up to 1000 characters)
- Run Key (required, up to 100 characters): a stable identifier your backend switches on, e.g.
daily-report - Cron Expression (required): built with the visual cron editor; the value is a six-field expression with seconds first
- Enabled toggle: new triggers default to off
- Click Create.
Invalid cron expressions are rejected when you save, so a trigger that exists always has a parseable schedule.
The trigger list
Each row shows the name, description, the cron expression, and an Enabled/Disabled badge. The two icons on the right open the Edit Trigger dialog and Delete (with a confirmation). There is no inline toggle: to enable or disable a trigger, open it and flip the Enabled switch, then click Update.
Editing a trigger
Open the trigger, change any field, click Update. A changed schedule applies from the next executor pass; a disabled trigger stops firing from the next pass.
Deleting a trigger
Click the delete icon and confirm. Deletion is permanent and takes effect at the next executor pass. Undelivered pending occurrences of that trigger are discarded with it.
All three trigger mutations write an audit row carrying the schedule, runKey and enabled state before and after, so a schedule change or a deletion is reconstructable from the row alone. See Audit Logs.
Cron expressions
Manager cron expressions have six fields; the first is seconds:
┌─────── second (0-59)
│ ┌────── minute (0-59)
│ │ ┌───── hour (0-23)
│ │ │ ┌──── day of month (1-31)
│ │ │ │ ┌─── month (1-12)
│ │ │ │ │ ┌── day of week (0-6, 0 = Sunday)
│ │ │ │ │ │
* * * * * *| Expression | Meaning |
|---|---|
0 0 * * * * | Every hour, on the hour |
0 */5 * * * * | Every 5 minutes |
0 0 0 * * * | Daily at midnight |
0 0 9 * * 1-5 | Weekdays at 09:00 |
0 30 14 * * * | Daily at 14:30 |
0 0 0 1 * * | First day of every month |
30 * * * * * | 30 seconds past every minute |
@hourly, @daily, @weekly, @monthly, @yearly | Shorthands, also accepted |
Schedules are evaluated in the Manager server's local time zone. Tools such as crontab.guru help with the last five fields; remember to add the seconds field in front.
Executor pass interval
The executor checks schedules every cron.trigger_check_interval (default 30 s) and dispatches every occurrence that fell in the window since its previous check, so a per-minute schedule is not missed under a 30 s interval. For per-second schedules set the interval to 1s. See Configuration.
What the backend receives
For every due occurrence the Manager POSTs to each registered backend's callback URL, with Authorization: Bearer <token from registration>:
{
"trigger_id": "uuid",
"trigger_name": "Daily Report",
"cron_expression": "0 0 0 * * *",
"run_key": "daily-report",
"executed_at": "2025-12-01T00:00:00Z"
}The payload is tenant-agnostic. A backend that needs to work per tenant lists them with GET /api/machine/application/:id/tenants and loops. A 2xx response counts as delivered; anything else, or no answer within 5 seconds, counts as an error.
Monitoring
- Application dashboard: lists the triggers with schedule and enabled state. Per-run outcomes are not recorded, so the last-run column stays empty.
- Executor metrics: Prometheus metrics on the executor's
cron.metrics_port(default 9090):pfy_executor_backend_dispatch_total{status="success"|"error"},pfy_executor_trigger_delivery_failed_total{app},pfy_executor_queue_waiting{app}and more; see Monitoring and metrics. - Manager logs: dispatch errors and blocked callback URLs are logged by the executor process.
- Backend health: the home dashboard flags registrations whose heartbeat is stale. A backend whose last heartbeat is older than
cron.backend_heartbeat_timeout(default 1 min) is marked inactive and stops receiving triggers until it heartbeats or re-registers.
Troubleshooting
The trigger never fires
Check that it is Enabled, that at least one process runs with run_mode executor or both, and that a backend for the application is registered and active. Look at pfy_executor_active_triggers and the executor log.
The backend does not receive the call
Confirm the registration's callback URL is reachable from the Manager and does not point at a private, loopback, link-local or cloud-metadata address; those are rejected at registration and again at dispatch. Check pfy_executor_backend_dispatch_total{status="error"} and the Manager log.
It fires at the wrong time
Count the fields (six, seconds first) and check the Manager server's time zone.
It fires more often than expected
A schedule with more than 60 occurrences in one check window is truncated to the first 60 and logged; shorten the interval or the schedule.
Related
- Trigger System: executor internals, callback contract, GraphQL API
- Backend Registration: implementing the callback
- Machine Users: credentials for backends
- Dashboard: where trigger and backend state is shown