# Cron Jobs in Bun: Scheduled Background Tasks

Schedule recurring jobs with cron expressions or intervals. Persist schedules in SQLite or coordinate them across PostgreSQL brokers, with timezone support.

Canonical: https://bunqueue.dev/guide/cron/

---

import { Tabs, TabItem } from '@astrojs/starlight/components';

<div class="bq-wrap bq-hero">
  <span class="bq-eyebrow">guide · cron jobs</span>
  <h1 class="bq-hero-h1 bq-bench-h1">Work that runs <em>on a clock.</em></h1>
  <p class="bq-hero-sub">Nightly reports, hourly cleanups, a health ping every thirty seconds. Schedules live in the selected persistent backend: beside jobs in SQLite, or transactionally coordinated across a PostgreSQL broker fleet. Memory-only mode does not survive a restart.</p>
</div>

A scheduler is a named rule that keeps producing jobs on a queue. Create it once with `upsertJobScheduler()` and bunqueue fires the job on every tick, whether that tick comes from a cron pattern or a fixed interval. Scheduler IDs are global to the selected backend—not scoped by queue—and, in PostgreSQL mode, shared by every broker in the namespace. Use names such as `reports:daily-report` when several applications share a deployment.

## Quick Start

Create a scheduler with `upsertJobScheduler`. It works in both embedded and TCP mode, in every SDK (Python: `upsert_job_scheduler`, Rust: `upsert_job_scheduler`, Go: `UpsertJobScheduler`, Elixir: `upsert_scheduler`), and calling it again with the same global ID replaces that scheduler definition instead of duplicating it. Reusing an ID with a different queue moves the definition to that queue.

<Tabs syncKey="lang">
<TabItem label="Bun">

```typescript
import { Queue, Worker } from 'bunqueue/client';

const queue = new Queue('reports', { embedded: true });

// Every day at 9:00 AM
await queue.upsertJobScheduler(
  'daily-report',
  {
    pattern: '0 9 * * *',
  },
  {
    name: 'daily-report',
    data: { type: 'sales' },
  }
);

// A normal worker processes the scheduled jobs
new Worker(
  'reports',
  async (job) => {
    console.log('Running report:', job.data.type);
  },
  { embedded: true }
);
```

</TabItem>
<TabItem label="Node.js / Deno">

```typescript
import { Queue, Worker } from 'bunqueue-client';

const queue = new Queue('reports');

// Every day at 9:00 AM
await queue.upsertJobScheduler(
  'daily-report',
  {
    pattern: '0 9 * * *',
  },
  {
    name: 'daily-report',
    data: { type: 'sales' },
  }
);

// A normal worker processes the scheduled jobs
new Worker('reports', async (job) => {
  console.log('Running report:', job.data.type);
});
```

</TabItem>
<TabItem label="Python">

```python
from bunqueue import Queue, Worker

queue = Queue("reports")

# Every day at 9:00 AM
queue.upsert_job_scheduler("daily-report",
    {"pattern": "0 9 * * *"},
    {"name": "daily-report", "data": {"type": "sales"}})

# A normal worker processes the scheduled jobs
def process(job):
    print("Running report:", job.data["type"])

Worker("reports", process).run()
```

</TabItem>
<TabItem label="PHP">

```php
use Bunqueue\Queue;
use Bunqueue\Worker;

$queue = new Queue('reports');

// Every day at 9:00 AM
$queue->upsertJobScheduler('daily-report',
    ['pattern' => '0 9 * * *'],
    ['name' => 'daily-report', 'data' => ['type' => 'sales']]);

// A normal worker processes the scheduled jobs
$worker = new Worker('reports', function (Bunqueue\Job $job) {
    $data = $job->data();
    echo "Running report: {$data['type']}\n";
});
$worker->run();
```

</TabItem>
<TabItem label="Go">

```go
queue := bunqueue.NewQueue("reports", bunqueue.Options{})

// Every day at 9:00 AM
err := queue.UpsertJobScheduler("daily-report",
    bunqueue.SchedulerRepeat{Pattern: "0 9 * * *"},
    bunqueue.SchedulerTemplate{
        Name: "daily-report",
        Data: map[string]any{"type": "sales"},
    })

// A normal worker processes the scheduled jobs
worker := bunqueue.NewWorker("reports", func(job *bunqueue.Job) (any, error) {
    fmt.Println("Running report:", job.Data()["type"])
    return nil, nil
}, bunqueue.WorkerOptions{})
worker.Run()
```

</TabItem>
<TabItem label="Rust">

```rust
use bunqueue_client::{
    ConnectionOptions, Queue, SchedulerRepeat, SchedulerTemplate, Value, Worker, WorkerOptions,
};

let queue = Queue::new("reports", ConnectionOptions::default());

// Every day at 9:00 AM
queue.upsert_job_scheduler(
    "daily-report",
    SchedulerRepeat { pattern: Some("0 9 * * *".into()), ..Default::default() },
    SchedulerTemplate {
        name: Some("daily-report".into()),
        data: Value::Map(vec![(Value::from("type"), Value::from("sales"))]),
        ..Default::default()
    },
)?;

// A normal worker processes the scheduled jobs
let worker = Worker::new(
    "reports",
    |job| {
        println!("Running report: {:?}", job.data());
        Ok(Value::Nil)
    },
    WorkerOptions::default(),
);
worker.run()?;
```

</TabItem>
<TabItem label="Elixir">

```elixir
queue = Bunqueue.queue("reports")

# Every day at 9:00 AM
:ok =
  Bunqueue.Queue.upsert_scheduler(queue, "daily-report",
    %{pattern: "0 9 * * *"},
    %{name: "daily-report", data: %{type: "sales"}}
  )

# A normal worker processes the scheduled jobs
worker =
  Bunqueue.Worker.new("reports", fn job ->
    IO.puts("Running report: #{job.data["type"]}")
    {:ok, nil}
  end)

Bunqueue.Worker.run(worker)
```

</TabItem>
</Tabs>

Or from the CLI, against a running server:

```bash
bunqueue cron add daily-report -q reports -d '{"type":"daily"}' -s "0 9 * * *"
bunqueue cron list
bunqueue cron delete daily-report
```

## Where to go next

|                                                           |                                                     |
| --------------------------------------------------------- | --------------------------------------------------- |
| [Job Schedulers from the Queue](/guide/queue/schedulers/) | Named repeatable schedules on the Queue object      |
| [Cron Recipes](/guide/cron/recipes/)                      | Fixed intervals, timezones, repeat-after-completion |
| [Cron Reference](/guide/cron/reference/)                  | Expression syntax, every scheduler option, MCP      |