Files
payloadcms/docs/jobs-queue/schedules.mdx
Alessio Gravili 2e9ba10fb5 docs: remove obsolete scheduler property (#13278)
That property does not exist and was used in a previous, outdated
implementation of auto scheduling
2025-07-25 16:25:47 -07:00

156 lines
6.7 KiB
Plaintext

---
title: Job Schedules
label: Schedules
order: 60
desc: Payload allows you to schedule jobs to run periodically
keywords: jobs queue, application framework, typescript, node, react, nextjs, scheduling, cron, schedule
---
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 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.
## Example use cases
**Regular emails or notifications**
Send nightly digests, weekly newsletters, or hourly updates.
**Batch processing during off-hours**
Process analytics data or rebuild static sites during low-traffic times.
**Periodic data synchronization**
Regularly push or pull updates to or from external APIs.
## 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.
## Defining schedules on Tasks or Workflows
Schedules are defined using the `schedule` property:
```ts
export type ScheduleConfig = {
cron: string // required, supports seconds precision
queue: string // required, the queue to push Jobs onto
hooks?: {
// Optional hooks to customize scheduling behavior
beforeSchedule?: BeforeScheduleFn
afterSchedule?: AfterScheduleFn
}
}
```
### Example schedule
The following example demonstrates scheduling a Job to enqueue every day at midnight:
```ts
import type { TaskConfig } from 'payload'
export const SendDigestEmail: TaskConfig<'SendDigestEmail'> = {
slug: 'SendDigestEmail',
schedule: [
{
cron: '0 0 * * *', // Every day at midnight
queue: 'nightly',
},
],
handler: async () => {
await sendDigestToAllUsers()
},
}
```
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):
```ts
export default buildConfig({
jobs: {
autoRun: [
{
cron: '* * * * *', // Runs every minute
queue: 'nightly',
},
],
tasks: [SendDigestEmail],
},
})
```
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:
1. **Cron evaluation**: Payload (or your external trigger in `manual` mode) identifies which schedules are due to run. To do that, it will
read the `payload-jobs-stats` global which contains information about the last time each scheduled task or workflow was run.
2. **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 `beforeSchedule` hook to customize this behavior. For example, you might want to allow multiple overlapping Jobs or dynamically set the Job input data.
3. **Enqueue Job**: Payload queues up a new job. This job will have `waitUntil` set to the next scheduled time based on the cron expression.
4. **AfterSchedule hook**:
- The default afterSchedule hook updates the `payload-jobs-stats` global 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 (Advanced)
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:
```ts
import { countRunnableOrActiveJobsForQueue } from 'payload'
schedule: [
{
cron: '* * * * *', // every minute
queue: 'reports',
hooks: {
beforeSchedule: async ({ queueable, req }) => {
const runnableOrActiveJobsForQueue =
await countRunnableOrActiveJobsForQueue({
queue: queueable.scheduleConfig.queue,
req,
taskSlug: queueable.taskConfig?.slug,
workflowSlug: queueable.workflowConfig?.slug,
onlyScheduled: true,
})
// Allow up to 3 simultaneous scheduled jobs and set dynamic input
return {
shouldSchedule: runnableOrActiveJobsForQueue < 3,
input: { text: 'Hi there' },
}
},
},
},
]
```
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 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).