---
title: "Jobs Queue — Queues & Execution"
aliases: [jobs-queue-execution, payload-queues, payload-job-runners]
tags: [payloadcms, jobs-queue, queues, cron, workers, autorun, vercel]
sources: [raw/jobs-queue__queues.md]
created: 2026-05-15
updated: 2026-05-15
---

## Overview

Queues are named channels for grouping jobs. All jobs go into `default` by default. Custom queue names allow different cron schedules (e.g. `nightly` vs `default`). This article covers **how to execute** queued jobs.

---

## Execution Methods

### 1. Bin Script (Recommended — Dedicated Servers)

Runs in a **separate process** from Next.js — preferred for production.

```sh
# Basic
pnpm payload jobs:run

# Custom queue + limit
pnpm payload jobs:run --queue myQueue --limit 15

# Cron schedule (production recommended)
pnpm payload jobs:run --cron "*/5 * * * *" --queue myQueue

# Also handle scheduled task queuing
pnpm payload jobs:run --cron "*/5 * * * *" --queue myQueue --handle-schedules

# All queues
pnpm payload jobs:run --all-queues
```

**docker-compose.yml pattern:**

```yaml
services:
  nextjs:
    command: pnpm start
  worker-default:
    command: pnpm payload jobs:run --cron "*/5 * * * *" --queue default
  worker-nightly:
    command: pnpm payload jobs:run --cron "* * * * *" --queue nightly --handle-schedules
```

Benefits: independent process, independent scaling, no Next.js overhead.

---

### 2. `autoRun` (Alternative — Dedicated Servers Only)

Runs within the Next.js process. **Only executes** already-queued jobs — does NOT queue new ones.

```ts
jobs: {
  autoRun: [
    { cron: '*/5 * * * *', queue: 'default', limit: 50 },
    { cron: '* * * * *',   queue: 'nightly', limit: 100 },
  ],
  shouldAutoRun: async (payload) => {
    return process.env.ENABLE_JOB_WORKERS === 'true'
  },
}
```

> **Warning:** Do not use `autoRun` on serverless platforms (Vercel, Netlify). Use the Endpoint method instead.

---

### 3. HTTP Endpoint (Serverless — Vercel/Netlify)

Auto-mounted at `/api/payload-jobs/run`.

```ts
await fetch('/api/payload-jobs/run?limit=100&queue=nightly', {
  method: 'GET',
  headers: { Authorization: `Bearer ${token}` },
})
```

Query params: `limit` (default 10), `queue` (default `'default'`), `allQueues=true`.

**Vercel Cron (`vercel.json`):**

```json
{
  "crons": [{ "path": "/api/payload-jobs/run", "schedule": "*/5 * * * *" }]
}
```

**Secure with `CRON_SECRET`:**

```ts
jobs: {
  access: {
    run: ({ req }) => {
      if (req.user) return true
      const secret = process.env.CRON_SECRET
      if (!secret) return false
      return req.headers.get('authorization') === `Bearer ${secret}`
    },
  },
}
```

Vercel automatically injects `CRON_SECRET` as the `Authorization` header when triggering the cron.

---

### 4. Local API (Programmatic / Testing)

```ts
// Run from default queue (limit 10)
await payload.jobs.run()

// Custom queue + limit
await payload.jobs.run({ queue: 'nightly', limit: 100 })

// All queues
await payload.jobs.run({ allQueues: true })

// Filtered run
await payload.jobs.run({ where: { 'input.message': { equals: 'secret' } } })

// Single job by ID
await payload.jobs.runByID({ id: myJobID })
```

---

## Processing Order

Default: FIFO (`createdAt` ascending).

```ts
// Global LIFO
jobs: { processingOrder: '-createdAt' }

// Per-queue
jobs: {
  processingOrder: {
    default: 'createdAt',         // FIFO
    queues: { nightly: '-createdAt' }, // LIFO
  },
}

// Dynamic function
jobs: {
  processingOrder: ({ queue }) =>
    queue === 'myQueue' ? '-createdAt' : 'createdAt',
}

// Per-job at queue time
await payload.jobs.queue({
  workflow: 'createPost',
  processingOrder: '-createdAt',
})
```

---

## Common Queue Strategies

### Priority-Based

```ts
autoRun: [
  { cron: '* * * * *',   queue: 'critical', limit: 100 }, // every minute
  { cron: '*/5 * * * *', queue: 'default',  limit: 50  }, // every 5 min
  { cron: '0 2 * * *',   queue: 'batch',    limit: 1000 }, // nightly 2 AM
]
```

### Feature-Based

```ts
autoRun: [
  { cron: '*/2 * * * *',  queue: 'emails',    limit: 100  },
  { cron: '*/10 * * * *', queue: 'images',    limit: 50   },
  { cron: '0 * * * *',    queue: 'analytics', limit: 1000 },
]
```

### Environment-Based (selective worker activation)

```ts
shouldAutoRun: async () => process.env.ENABLE_JOB_WORKERS === 'true'
```

---

## Execution Method Decision Table

| Method | Best For | Pros | Cons |
|--------|----------|------|------|
| **Bin script** | Dedicated servers (recommended) | Separate process, easy scaling | Requires dedicated server |
| **autoRun** | Dedicated servers (alternative) | Simple setup | Runs in Next.js process |
| **Endpoint** | Serverless (Vercel, Netlify) | Works serverless | Needs external cron trigger |
| **Local API** | Testing, custom scheduling | Full control | Must implement scheduling |

**Recommendations:**
- Dedicated server → Bin script with `--cron`
- Serverless → Endpoint + Vercel Cron
- Development → Bin script or Local API
- Tests → Local API with `runByID()`

---

## Troubleshooting

### Jobs not running

1. **No jobs queued** — `autoRun` executes jobs but doesn't queue them. Add `payload.jobs.queue()` calls or use `schedule` on tasks.
2. **Queue name mismatch** — `schedule: [{ queue: 'reports' }]` must match `autoRun: [{ queue: 'reports' }]`.
3. **Serverless platform** — `autoRun` won't survive request lifecycle on Vercel. Use Endpoint method.
4. **`waitUntil` in the future** — check `payload-jobs` collection for future `waitUntil` values.
5. **HMR in dev** — restart dev server after changing job config.

### Debug checklist

```ts
// Verify shouldAutoRun fires
shouldAutoRun: async () => { console.log('shouldAutoRun called'); return true }

// Make jobs collection visible
jobsCollectionOverrides: ({ defaultJobsCollection }) => ({
  ...defaultJobsCollection,
  admin: { ...defaultJobsCollection.admin, hidden: false },
})
```

In admin, look for:
- `processing: true` (stuck) → worker crashed
- `hasError: true` → check `log` field
- `completedAt: null` → not yet run

### Jobs running slowly

- Increase `limit` in `autoRun`
- Decrease cron interval (`* * * * *` = every minute)
- Scale horizontally: multiple servers with `ENABLE_JOB_WORKERS=true`

---

## Key Takeaways

- `queue` is a named channel — assign at `payload.jobs.queue({ queue: 'nightly' })`
- **Four execution methods**: bin script (recommended), `autoRun`, HTTP endpoint, Local API
- `autoRun` only **runs** jobs — it does NOT queue them; use `schedule` on tasks or manual `payload.jobs.queue()` to queue
- **Serverless**: never use `autoRun`; use `/api/payload-jobs/run` + Vercel Cron + `CRON_SECRET`
- Queue name in `schedule` / `queue()` must exactly match queue name in `autoRun` / bin script
- Bin script = separate process → independent scaling, no Next.js overhead
- Default processing order is FIFO; override globally, per-queue, or per-job with `processingOrder`
- Enable `admin: { hidden: false }` on `payload-jobs` collection for debugging

---

## Related

- [[wiki/payloadcms/jobs-queue|Jobs Queue — Overview]]
- [[wiki/payloadcms/jobs-queue-jobs|Jobs Queue — Jobs Detail]]
- [[wiki/payloadcms/configuration|Payload Config — Overview]]
- [[wiki/payloadcms/production|Production & Performance]]
