Bun.cron: Native OS-Level Cron Jobs in JavaScript
Bun v1.3.11 introduces Bun.cron, a built-in API for registering OS-level cron jobs, parsing cron expressions, and managing scheduled tasks—all without any external dependencies. This feature brings the power of system schedulers directly into your JavaScript applications with a clean, cross-platform API.
What is Bun.cron?
Bun.cron is a zero-dependency solution for scheduling recurring tasks that integrates with your operating system's native scheduler. Whether you're building a background worker, a data pipeline, or a maintenance script, you can now register cron jobs programmatically from JavaScript instead of manually editing crontab files or setting up Windows Task Scheduler tasks.
The API follows the Cloudflare Workers Cron Triggers pattern, making it familiar for developers who've worked with edge computing platforms.
Registering Cron Jobs
The Bun.cron() function registers a job with your OS scheduler:
await Bun.cron("./cleanup-worker.ts", "30 2 * * MON", "weekly-cleanup");
When the OS scheduler fires, Bun imports your script and calls the scheduled() method on the default export:
export default {
async scheduled(controller) {
console.log(`Running at ${new Date(controller.scheduledTime).toISOString()}`);
await performCleanup();
},
};
The controller object provides:
cron: The cron expression that triggered this executionscheduledTime: Unix timestamp (in milliseconds) when the job was scheduled to run
Removing Scheduled Jobs
Remove a registered job by its name:
await Bun.cron.remove("weekly-cleanup");
Re-registering with the same name overwrites the existing job in-place.
Cron Expression Parsing
Bun includes a built-in cron expression parser via Bun.cron.parse():
const nextQuarter = Bun.cron.parse("*/15 * * * *");
const weekdayMorning = Bun.cron.parse("0 9 * * MON-FRI");
const newYear = Bun.cron.parse("@yearly");
console.log(nextQuarter);
The function returns a Date object representing the next occurrence, or null if no match exists within approximately 4 years.
Parsing from a Specific Time
Parse from a specific starting point:
const from = new Date("2024-06-15T10:00:00Z");
const first = Bun.cron.parse("0 * * * *", from);
const second = Bun.cron.parse("0 * * * *", first);
Cron Expression Features
Standard 5-Field Format
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-7, 0 or 7 = Sunday)
│ │ │ │ │
* * * * *
Supported operators:
*— any value,— value list separator (e.g.,1,15,30)-— range (e.g.,MON-FRI)/— step values (e.g.,*/15)
Named Days and Months
Bun.cron.parse("0 9 * * MON");
Bun.cron.parse("0 9 * * Friday");
Bun.cron.parse("0 9 * * MON-FRI");
Bun.cron.parse("0 0 1 JAN *");
Bun.cron.parse("0 0 1 JAN-MAR *");
Nicknames
| Nickname | Equivalent | Description |
|---|---|---|
@yearly | 0 0 1 1 * | Once a year (Jan 1, midnight) |
@monthly | 0 0 1 * * | First day of each month |
@weekly | 0 0 * * 0 | Once a week (Sunday midnight) |
@daily | 0 0 * * * | Once a day (midnight) |
@hourly | 0 * * * * | Once an hour |
POSIX OR Logic
When both day-of-month and day-of-week are restricted, the job fires when either condition matches:
Bun.cron("./task.ts", "0 9 1 * MON", "monthly-or-monday");
Platform Backends
Bun.cron integrates with each OS's native scheduler:
| Platform | Backend | View Logs |
|---|---|---|
| Linux | crontab | journalctl -u cron |
| macOS | launchd plist | /tmp/bun.cron.<title>.{stdout,stderr}.log |
| Windows | schtasks | Event Viewer → TaskScheduler |
Practical Examples
Database Maintenance
await Bun.cron("./db-vacuum.ts", "0 3 * * SUN", "db-vacuum");
import { sql } from "bun";
export default {
async scheduled(controller) {
console.log(`Running database vacuum at ${new Date().toISOString()}`);
await sql`VACUUM ANALYZE`;
console.log("Database maintenance complete");
},
};
Log Rotation
await Bun.cron("./rotate-logs.ts", "0 0 * * *", "log-rotation");
import { $ } from "bun";
export default {
async scheduled() {
const date = new Date().toISOString().split("T")[0];
await $`gzip -c logs/app.log > logs/app-${date}.log.gz`.quiet();
await $`truncate -s 0 logs/app.log`.quiet();
await $`find logs/ -name "app-*.log.gz" -mtime +30 -delete`.quiet();
},
};
Cache Warming
await Bun.cron("./warm-cache.ts", "*/30 * * * *", "cache-warm");
const ENDPOINTS = [
"/api/products/featured",
"/api/categories",
"/api/reviews/recent",
];
export default {
async scheduled() {
const baseUrl = process.env.API_BASE_URL || "http://localhost:3000";
await Promise.all(
ENDPOINTS.map(async (endpoint) => {
const response = await fetch(`${baseUrl}${endpoint}`);
console.log(`Warmed ${endpoint}: ${response.status}`);
})
);
},
};
Bun vs Node.js
Node.js Approach
npm install node-cron cron-parser
const cron = require("node-cron");
const { parseCronExpression } = require("cron-parser");
const job = cron.schedule("30 2 * * MON", async () => {
console.log("Running task...");
await doWork();
});
const interval = parseCronExpression("30 2 * * MON");
const next = interval.next().toDate();
job.stop();
job.start();
Issues with Node.js:
- Jobs run in-process—if the process crashes, jobs are lost
- No OS-level persistence across reboots
- Requires separate packages for scheduling and parsing
- No native integration with system logs
- Must manage job lifecycle manually
Bun Approach
await Bun.cron("./worker.ts", "30 2 * * MON", "weekly-task");
const next = Bun.cron.parse("30 2 * * MON");
await Bun.cron.remove("weekly-task");
| Feature | Node.js | Bun |
|---|---|---|
| External packages | Required | Built-in |
| OS-level scheduling | No | Yes |
| Survives reboots | No | Yes |
| System log integration | Manual | Automatic |
| Cross-platform | Manual handling | Automatic |
| Process independence | Tied to process | Independent |
Key advantages of Bun.cron:
- Persistence: Bun.cron registers jobs with the OS scheduler, surviving restarts and reboots
- Zero dependencies: Everything is built-in
- System integration: Logs flow to journald, launchd, or Event Viewer automatically
- Reliability: OS schedulers are battle-tested for decades
Getting Started
Upgrade to Bun v1.3.11 or later:
bun upgrade
Register your first cron job:
await Bun.cron("./my-task.ts", "@hourly", "hourly-task");
console.log("Cron job registered!");
Run the registration script:
bun run register.ts
Conclusion
Bun.cron eliminates the friction of scheduling recurring tasks in JavaScript applications. By integrating directly with OS schedulers, it provides reliability and persistence that in-process solutions cannot match. Combined with Bun's zero-dependency philosophy, you get production-ready cron functionality without adding a single package to your project.