Job Schedules
Payload's schedule property lets you enqueue Jobs regularly according to a cron schedule - daily, weekly, hourly, or any custom interval. This is ideal for tasks or workflows that must repeat automatically and without manual intervention.
Scheduling Jobs differs significantly from running them:
- Queueing: Scheduling only creates (enqueues) the Job according to your cron expression. It does not immediately execute any business logic.
- Running: Execution happens separately through your Jobs runner - such as autorun, or manual invocation using
payload.jobs.run()or thepayload-jobs/runendpoint.
Use the schedule property specifically when you have recurring tasks or workflows. To enqueue a single Job to run once in the future, use the waitUntil property instead.
When to use Schedules
Here's a quick guide to help you choose the right approach:
Approach | Use Case | Example |
|---|---|---|
Schedule | Recurring tasks that run automatically on a schedule | Daily reports, weekly emails, hourly syncs |
waitUntil | One-time job in the future | Publish a post at 3pm tomorrow, send trial expiry email in 7 days |
Collection Hook | Job triggered by document changes | Send email when post is published, generate PDF when order is created |
Manual Queue | Job triggered by user action or API call | User clicks "Generate Report" button |
Example comparison:
Handling schedules
Something needs to actually trigger the scheduling of jobs (execute the scheduling lifecycle seen below). By default, the jobs.autorun configuration, as well as the /api/payload-jobs/run will also handle scheduling for the queue specified in the autorun configuration.
You can disable this behavior by setting disableScheduling: true in your autorun configuration, or by passing disableScheduling=true to the /api/payload-jobs/run endpoint. This is useful if you want to handle scheduling manually, for example, by using a cron job or a serverless function that calls the /api/payload-jobs/handle-schedules endpoint or the payload.jobs.handleSchedules() local API method.
Bin Scripts
Payload provides a set of bin scripts that can be used to handle schedules. If you're already using the jobs:run bin script, you can set it to also handle schedules by passing the --handle-schedules flag:
If you only want to handle schedules, you can use the dedicated jobs:handle-schedules bin script:
Defining schedules on Tasks or Workflows
Schedules are defined using the schedule property:
Example schedule
The following example demonstrates scheduling a Job to enqueue every day at midnight:
This configuration only queues the Job - it does not execute it immediately. To actually run the queued Job, you configure autorun in your Payload config (note that autorun should not be used on serverless platforms):
That way, Payload's scheduler will automatically enqueue the job into the nightly queue every day at midnight. The autorun configuration will check the nightly queue every minute and execute any Jobs that are due to run.
Scheduling lifecycle
Here's how the scheduling process operates in detail:
- Cron evaluation: Payload (or your external trigger in
manualmode) identifies which schedules are due to run. To do that, it will read thepayload-jobs-statsglobal which contains information about the last time each scheduled task or workflow was run. - BeforeSchedule hook:
- The default beforeSchedule hook checks how many active or runnable jobs of the same type that have been queued by the scheduling system currently exist. If such a job exists, it will skip scheduling a new one.
- You can provide your own
beforeSchedulehook to customize this behavior. For example, you might want to allow multiple overlapping Jobs or dynamically set the Job input data.
- Enqueue Job: Payload queues up a new job. This job will have
waitUntilset to the next scheduled time based on the cron expression. - AfterSchedule hook:
- The default afterSchedule hook updates the
payload-jobs-statsglobal metadata with the last scheduled time for the Job. - You can provide your own afterSchedule hook to it for custom logging, metrics, or other post-scheduling actions.
Customizing concurrency and input
You may want more control over concurrency or dynamically set Job inputs at scheduling time. For instance, allowing multiple overlapping Jobs to be scheduled, even if a previously scheduled job has not completed yet, or preparing dynamic data to pass to your Job handler:
This allows fine-grained control over how many Jobs can run simultaneously and provides dynamically computed input values each time a Job is scheduled.
Scheduling in serverless environments
On serverless platforms, scheduling must be triggered externally since Payload does not automatically run cron schedules in ephemeral environments. You have two main ways to trigger scheduling manually:
- Invoke via Payload's API:
payload.jobs.handleSchedules() - Use the REST API endpoint:
/api/payload-jobs/handle-schedules - Use the run endpoint, which also handles scheduling by default:
GET /api/payload-jobs/run
For example, on Vercel, you can set up a Vercel Cron to regularly trigger scheduling:
- Vercel Cron Job: Configure Vercel Cron to periodically call
GET /api/payload-jobs/handle-schedules. If you would like to auto-run your scheduled jobs as well, you can use theGET /api/payload-jobs/runendpoint.
Once Jobs are queued, their execution depends entirely on your configured runner setup (e.g., autorun, or manual invocation).
Common Schedule Patterns
Here are typical cron expressions for common scheduling needs:
Cron format reference:
Real-World Examples
Daily digest email:
Weekly report generation:
Hourly data sync:
Troubleshooting Schedules
Here are a few things to check when scheduled jobs are not being queued:
Is schedule handling enabled?
Is the cron expression valid?
Test your cron expressions at crontab.guru (for 5-field format).
Check the payload-jobs-stats global
Scheduled Jobs queued but not running
This means scheduling is working, but execution isn't. See the Queues troubleshooting section.
Jobs running at wrong times
Issue: Job scheduled for midnight but runs immediately
This happens when waitUntil isn't set properly. Check your schedule config:
Multiple instances of the same scheduled job
By default, Payload prevents duplicate scheduled jobs. If you're seeing duplicates:
Are you running multiple servers without coordination?
If multiple servers are handling schedules, they might each queue jobs. Solution: Only enable schedule handling on one server:
Custom beforeSchedule hook
If you have a custom beforeSchedule hook, make sure it properly checks for existing jobs: