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:
payload.jobs.run() or the payload-jobs/run endpoint.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.
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 |
Define the schedule property on your task or workflow to automatically queue jobs:
This handles the QUEUING—it adds jobs to the database on a schedule.
Configure how queued jobs are executed. For dedicated servers, use bin scripts (recommended):
Or use autoRun (alternative):
This handles the RUNNING—it executes the job handler functions.
Before wondering why your scheduled jobs aren't working, verify:
schedule property? → Jobs will be queued automaticallyautoRun or another runner? → Queued jobs will be executedqueue name? → Jobs will flow from schedule to executionautoRun requires a long-running serverIf any checkbox is missing, your scheduled jobs won't work properly.
Example comparison:
Something needs to actually trigger the scheduling of jobs (execute the scheduling lifecycle seen below). There are several ways to handle scheduling:
For dedicated servers, the recommended approach is to use Payload's bin scripts. This runs the scheduling logic in a separate process from your Next.js server.
Combined scheduling and running (recommended):
Schedule-only (if you want separate processes for scheduling vs running):
Benefits of bin scripts:
By default, the jobs.autoRun configuration handles scheduling for the queue specified in the autoRun configuration:
You can disable this behavior by setting disableScheduling: true in your autoRun configuration if you want to handle scheduling separately.
For serverless environments, you can trigger scheduling via the /api/payload-jobs/run endpoint (which handles scheduling by default) or use the dedicated /api/payload-jobs/handle-schedules endpoint:
You can also handle scheduling programmatically:
Schedules are defined using the schedule property:
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 have several options:
Option 1: Bin script (recommended for dedicated servers):
Option 2: autoRun (alternative for dedicated servers):
Option 3: API endpoint (for serverless platforms):
Configure Vercel Cron or similar to trigger:
With any of these options, Payload's scheduler will automatically enqueue the job into the nightly queue every day at midnight, and the runner will check the nightly queue every minute to execute any Jobs that are due to run.
Here's how the scheduling process operates in detail:
manual mode) identifies which schedules are due to run. To do that, it willpayload-jobs-stats global which contains information about the last time each scheduled task or workflow was run.beforeSchedule hook to customize this behavior. For example, you might want to allow multiple overlapping Jobs or dynamically set the Job input data.waitUntil set to the next scheduled time based on the cron expression.payload-jobs-stats global metadata with the last scheduled time for the Job.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.
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:
payload.jobs.handleSchedules()/api/payload-jobs/handle-schedulesGET /api/payload-jobs/runFor example, on Vercel, you can set up a Vercel Cron to regularly trigger scheduling:
GET /api/payload-jobs/handle-schedules. If you would like to auto-run your scheduled jobs as well, you can use the GET /api/payload-jobs/run endpoint.Once Jobs are queued, their execution depends entirely on your configured runner setup (e.g., autorun, or manual invocation).
Here are typical cron expressions for common scheduling needs:
Cron format reference:
Daily digest email:
Weekly report generation:
Hourly data sync:
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
This means scheduling is working, but execution isn't. See the Queues troubleshooting section.
Issue: Job scheduled for midnight but runs immediately
This happens when waitUntil isn't set properly. Check your schedule config:
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:
If you have a custom beforeSchedule hook, make sure it properly checks for existing jobs: