Cron Jobs
Cron jobs schedule registered tasks from cron-like definitions. They are useful for recurring work such as cleanup, reporting, reconciliation, and periodic synchronization.
Cron entries can be added with typed Rust builders or with crontab text:
WorkerOptions::with_cron(...)accepts one cron entry. It accepts typed builders, rawCrontabvalues, and crontab text.WorkerOptions::with_crons(...)accepts multiple typed or rawCrontabvalues.WorkerOptions::with_crontab(...)accepts crontab text, but is deprecated in favor ofwith_cron(...).
Typed cron builders
Use Cron when the schedule belongs to a Rust TaskHandler. The task
identifier comes from T::IDENTIFIER, so it stays aligned with the job you
registered with define_job.
use graphile_worker::{
Cron, CrontabFill, IntoTaskHandlerResult, TaskHandler, WorkerContext,
WorkerOptions,
};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
struct SendDigest {
account_id: i64,
}
impl TaskHandler for SendDigest {
const IDENTIFIER: &'static str = "send_digest";
async fn run(self, _ctx: WorkerContext) -> impl IntoTaskHandlerResult {
// Send the digest.
}
}
let worker = WorkerOptions::default()
.define_job::<SendDigest>()
.with_cron(
Cron::daily_at::<SendDigest>(8, 0)?
.id("send_digest_morning")
.fill(CrontabFill::hours(1))
.payload(SendDigest { account_id: 42 })?,
)
.pg_pool(pg_pool)
.init()
.await?;
Typed schedules are built with:
Cron::every_minute::<MyTask>()
Cron::every_n_minutes::<MyTask>(5)?
Cron::hourly_at::<MyTask>(15)?
Cron::daily_at::<MyTask>(8, 0)?
Cron::weekly_on::<MyTask>(chrono::Weekday::Mon, 8, 0)?
Cron::monthly_on::<MyTask>(1, 8, 0)?
Cron::yearly_on::<MyTask>(1, 1, 8, 0)?
The typed constructors validate field ranges. Minutes are 0..=59, hours are
0..=23, days of month are 1..=31, and months are 1..=12.
Builder options include:
Cron::every_n_minutes::<SendDigest>(10)?
.id("send_digest_every_10_minutes")
.fill(CrontabFill::minutes(30))
.max_attempts(5)
.queue("digest")
.priority(-10)
.job_key("send_digest_dedupe")
.job_key_mode(graphile_worker::CronJobKeyMode::PreserveRunAt)
.payload(SendDigest { account_id: 42 })?;
Use payload(...) for a typed payload that serializes as JSON. Use
payload_value(...) when you already have a serde_json::Value.
Multiple cron entries
Use with_crons for a collection of typed builders or Crontab values:
let worker = WorkerOptions::default()
.define_job::<SendDigest>()
.define_job::<SyncMetrics>()
.with_crons([
Cron::daily_at::<SendDigest>(8, 0)?.build(),
Cron::every_n_minutes::<SyncMetrics>(15)?.build(),
])
.pg_pool(pg_pool)
.init()
.await?;
Crontab text
with_cron also accepts crontab text and returns a Result because the string
is parsed at runtime.
let worker = WorkerOptions::default()
.define_job::<SendDigest>()
.with_cron(
r#"
Run at 08:00 UTC every day.
0 8 * * * send_digest ?id=send_digest_morning&fill=1h {account_id:42}
"#,
)?
.pg_pool(pg_pool)
.init()
.await?;
The text format is:
* * * * * task_identifier ?options {payload}
The five schedule fields are UTC minute, UTC hour, UTC day of month, UTC month,
and UTC day of week. Days of week use 0..=6.
Supported schedule values are:
*for every valid value.*/nfor every value divisible byn.nfor one explicit value.a-bfor an inclusive range.- Comma-separated combinations such as
0,15,30,45or4,10-15.
Examples:
# Every minute.
* * * * * tick
# Every 4 hours at minute 0.
0 */4 * * * rollup
# Mondays at 04:30 UTC.
30 4 * * 1 send_weekly_email
# More than one value in a field.
30 4,10-15 * * 1 send_weekly_email
Task identifiers must start with a letter or underscore. After that they may contain letters, numbers, colon, underscore, or hyphen.
Options
Crontab text options use query-string syntax and must start with ?.
0 8 * * * send_digest ?id=send_digest_morning&fill=1h&max=5&queue=digest&priority=10
Supported options are:
id: stable identifier for this cron entry. By default the task identifier is used. Set this when the same task has more than one schedule.fill: backfill duration for missed executions.max: overrides the job's maximum attempts.queue: schedules the job in a named queue.priority: overrides the job priority.job_key: sets a job key for deduplication.job_key_mode: controls an existing unlocked job with the same key. Supported serialized values arereplaceandpreserve_run_at.
Changing a cron entry identifier can cause the worker to treat it as a new cron
entry. Prefer setting a stable id when a task has multiple cron schedules.
Payloads
Cron jobs receive a JSON object payload. The worker adds a _cron object to the
payload for each scheduled job:
{
"_cron": {
"ts": "2026-06-16T08:00:00",
"backfilled": false
}
}
ts is the timestamp when the cron execution was due, and backfilled is
true when the job was scheduled as a backfill.
With typed builders, set the payload with payload(...) or
payload_value(...). With crontab text, write a JSON5 object after the options:
0 8 * * * send_digest ?id=send_digest_morning {account_id:42, urgent:false}
The parsed payload is merged with the default cron payload properties. The text
payload must start with { and stay on one line.
Backfill and fill
By default, cron entries do not backfill missed executions. Add fill to ask
the worker to schedule missed runs from a recent time window when it starts or
recovers a known cron entry.
Typed builder:
Cron::every_n_minutes::<SyncMetrics>(10)?
.id("sync_metrics")
.fill(CrontabFill::minutes(30))
Crontab text:
*/10 * * * * sync_metrics ?id=sync_metrics&fill=30m
Fill durations are made from time units:
s: secondsm: minutesh: hoursd: daysw: weeks
Units may be combined in order, for example 1w2d3h30m. In Rust, use
CrontabFill::seconds, minutes, hours, days, weeks, or
CrontabFill::new(w, d, h, m, s).
Backfill only applies to cron entries that were already known to the worker.
When a cron entry is first registered, the worker records it as known but does
not immediately create old jobs for it. Tests cover this behavior by asserting
that a newly registered cron entry has no last_execution and creates no
backfill jobs.
When a known cron entry has a previous execution timestamp, the worker can catch
up missed matching schedule times inside the configured fill window. A four-hour
cron with fill=1d, for example, can create the missed four-hour executions
from the last day. Larger fill windows can schedule more jobs and take longer at
startup.
Runtime behavior
The cron runner checks schedule times and inserts regular jobs for matching entries. If time advances by multiple matching intervals, the runner catches up by scheduling the missed ticks it observes. Jobs are then processed by the same registered task handlers as jobs added through the regular job APIs.