7.2 KiB
| title | aliases | tags | sources | created | updated | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Jobs Queue — Tasks |
|
|
|
2026-05-15 | 2026-05-15 |
Overview
A Task is a strongly-typed function declaration registered in the Payload config that performs isolated business logic. Tasks are the atomic unit of the wiki/payloadcms/jobs-queue — Jobs and Workflows call Tasks.
Key trait: Tasks support automatic retries, making them ideal for non-deterministic work (AI/LLM calls, external API calls, payment processing).
Defining Tasks
Tasks live in jobs.tasks[] in buildConfig:
jobs: {
tasks: [
{
slug: 'createPost', // unique across tasks AND workflows
retries: 2,
inputSchema: [
{ name: 'title', type: 'text', required: true },
],
outputSchema: [
{ name: 'postID', type: 'text', required: true },
],
handler: async ({ input, job, req }) => {
const newPost = await req.payload.create({
collection: 'post',
data: { title: input.title },
})
return { output: { postID: newPost.id } }
},
} as TaskConfig<'createPost'>,
],
}
Task Config Options
| Option | Description |
|---|---|
slug |
Unique identifier — must be unique across tasks and workflows |
handler |
Function async ({ input, job, req, tasks, inlineTask }) => { output: {...} } or absolute file path |
inputSchema |
Payload field definitions — Payload generates TypeScript types from these |
outputSchema |
Same — generated output types |
retries |
Number of retries on failure; 0 = no retry; undefined = inherit from workflow |
retries.shouldRestore |
Boolean or function — whether to skip re-running a previously-passed task |
onFail |
Callback executed after all retries are exhausted |
onSuccess |
Callback executed on successful completion |
label |
Human-readable display name |
interfaceName |
Override generated TS interface name (default: Task + capitalized slug) |
concurrency |
Exclusive execution key — requires jobs.enableConcurrencyControl: true |
schedule |
Auto-queue on cron schedule — see [[wiki/payloadcms/jobs-queue-schedules |
Scheduling Tasks
Add schedule to auto-queue the task without calling payload.jobs.queue() manually:
{
slug: 'dailyDigest',
schedule: [
{ cron: '0 8 * * *', queue: 'daily' },
],
handler: async ({ req }) => {
// ... send emails
return { output: { emailsSent: 42 } }
},
}
Critical: schedule only queues jobs. You must also configure a runner with the same queue name:
autoRun: [
{ cron: '* * * * *', queue: 'daily', limit: 10 },
]
Common cron patterns:
{ cron: '0 * * * *', queue: 'hourly' } // every hour
{ cron: '0 0 * * *', queue: 'nightly' } // midnight
{ cron: '0 9 * * 1', queue: 'weekly' } // Monday 9AM
{ cron: '*/5 * * * *', queue: 'frequent' } // every 5 min
{ cron: '*/3 * * * * *',queue: 'realtime' } // every 3 sec (6-field syntax)
Common Patterns
Database Operations
{ slug: 'updateRelatedPosts', retries: 2,
handler: async ({ input, req }) => {
const posts = await req.payload.find({ collection: 'posts', where: { category: { equals: input.categoryId } } })
for (const post of posts.docs) {
await req.payload.update({ collection: 'posts', id: post.id, data: { categoryUpdatedAt: new Date().toISOString() } })
}
return { output: { postsUpdated: posts.docs.length } }
}
}
External API Calls
{ slug: 'syncToThirdParty', retries: 3,
handler: async ({ input, req }) => {
const response = await fetch('https://api.example.com/sync', { method: 'POST', body: JSON.stringify(doc) })
if (!response.ok) throw new Error(`API error: ${response.statusText}`)
return { output: { synced: true, apiResponse: await response.json() } }
}
}
Handling Failures
- Throw errors with descriptive messages — stored in
job.errorand visible in Admin UI - Prevent retries by throwing
JobCancelledError:throw new JobCancelledError('Job was cancelled') - Inspect failures after the fact:
const job = await payload.findByID({ collection: 'payload-jobs', id: jobId }) if (job.hasError) { console.log(job.error) const failedTasks = job.log?.filter(e => e.state === 'failed') }
Task Execution Lifecycle
- Worker picks up the job from the queue
- Handler executes with provided
input - Success → output stored, job completes
- Error thrown → task retried (up to
retriescount) - All retries exhausted → task and job fail
Idempotency: Tasks should produce the same result when run multiple times with the same input, since retries cause repeated execution.
Task Restoration (shouldRestore)
By default, if a workflow is re-run and a task previously passed, the task is skipped and previous output is reused.
Override with retries.shouldRestore:
true(default) — skip if previously passedfalse— always re-run, even if previously passedfunction({ input }) => boolean— custom logic (e.g. re-run if date has changed)
retries: {
shouldRestore: ({ input }) => new Date(input.someDate) > new Date() ? false : true,
}
Nested Tasks
Tasks can call sub-tasks via tasks (registered tasks) and inlineTask (anonymous functions):
handler: async ({ tasks, inlineTask }) => {
await inlineTask('Sub Task 1', { task: () => ({ output: {} }) })
await tasks.CreateSimple('Sub Task 2', { input: { message: 'hello' } })
return { output: {} }
}
Enable jobs.addParentToTaskLog: true for better observability when using nested tasks.
Handler File Paths (Advanced)
Pass an absolute path + named export instead of an inline function to avoid bundling large dependencies in Next.js:
handler: path.resolve(dirname, 'src/tasks/createPost.ts') + '#createPostHandler'
Requires separate transpilation of handler files and a sophisticated build pipeline. Prefer inline functions unless bundle size is a real concern.
Key Takeaways
- Tasks are the atomic unit: one slug, one handler, strongly-typed input/output
retriesenables durable AI/external API workflows — always set for non-deterministic operationsschedule+ matchingautoRunqueue = periodic auto-execution without manual queuing- Throw errors for failures; use
JobCancelledErrorto stop all retries immediately - Tasks are idempotent by default via
shouldRestore— re-runs skip already-passed tasks - Handler file paths are an escape hatch for large dependencies; avoid unless necessary
- Enable
addParentToTaskLog: truewhen using nested tasks