Quickstart
Run a callback on a schedule in the current process:Bun.cron.parse()
Parse a cron expression and return the next matching Date in the system’s local time zone.
Parameters
Returns
Date | null — the next matching time, or null if no match exists within 8 years (for example, February 30th).
Chaining calls
Callparse() repeatedly to get a sequence of upcoming times:
Cron expression syntax
Standard 5-field format:minute hour day-of-month month day-of-week
Special characters
Named values
Month and weekday fields accept case-insensitive names:0 and 7 mean Sunday in the weekday field.
Predefined nicknames
Time zone
Bun interprets schedules in the system’s local time zone, the same way crontab, launchd, and Windows Task Scheduler read them. The OS-level form and the in-process callback form fire at the same wall-clock time. To override, pass an IANA time-zone name as{ tz } to Bun.cron.parse() or the in-process Bun.cron(schedule, handler, options):
- Spring-forward — a schedule that lands in the missing hour fires that day, shifted forward by the gap (e.g.
30 2 * * *runs at 3:30 on the spring-forward day). For multi-minute patterns inside the gap (*/15 2 * * *), only the first match fires. - Fall-back — a fixed-time schedule in the duplicated hour (
30 1 * * *) fires once, at the first occurrence. A schedule whose minute or hour field is*(0 * * * *,* * * * *) fires through both occurrences — once per real-time minute, matching crontab on Linux.
Day-of-month and day-of-week interaction
When you specify both day-of-month and day-of-week (neither is*), the expression matches when either condition is true. This follows the POSIX cron standard.
*), Bun uses only that field for matching.
Bun.cron(schedule, handler) — in-process
Run a callback on a cron schedule inside the current process.
Parameters
Returns a
CronJob synchronously. Throws a TypeError if the expression is invalid, the time-zone name is unknown, or the expression has no future occurrences, like "0 0 30 2 *" (February 30th).
No-overlap guarantee
Bun computes the next fire time only after the handler and any returnedPromise settle. If your handler takes 90 seconds and the schedule is * * * * *, the second fire is the first minute boundary after the handler finishes, not 60 seconds after the first fire. Invocations never stack.
Error handling
Errors matchsetTimeout semantics:
- A synchronous
throwemitsprocess.on("uncaughtException"). - A rejected returned
Promiseemitsprocess.on("unhandledRejection").
1. With a listener, the job keeps running — it does not stop on the first failure.
bun --hot
Under bun --hot, Bun stops all in-process cron jobs immediately before the module graph re-evaluates. Every Bun.cron() call still in your source then re-registers. Editing the schedule, editing the handler, or deleting the line entirely all take effect on save without leaking timers.
The CronJob handle
CronJob is Disposable — using job = Bun.cron(...) auto-stops at scope exit. stop(), ref(), and unref() all return the job for chaining.
Fake timers
In-process cron honorsjest.useFakeTimers(). setSystemTime(), advanceTimersByTime(), and runAllTimers() control when it fires, so you can test scheduled callbacks without waiting on the real clock.
Bun.cron(path, schedule, title) — OS-level
Register an OS-level cron job that runs a JavaScript/TypeScript module on a schedule.
Parameters
Re-registering with the same
title overwrites the existing job in-place. Bun replaces the old schedule instead of duplicating it.
The scheduled() handler
The registered script must export a default object with a scheduled() method, following the Cloudflare Workers Cron Triggers API:
worker.ts
async. Bun waits for the returned promise to settle before exiting.
How it works per platform
Linux
Bun uses crontab to register jobs. Bun stores each job as a line in your user’s crontab with a# bun-cron: <title> marker comment above it.
The crontab entry looks like:
scheduled() handler.
Viewing registered jobs:
scheduled() handler.
Manually uninstalling without code:
macOS
Bun uses launchd to register jobs. Bun installs each job as a plist file at:StartCalendarInterval to define the schedule. Complex patterns with ranges, lists, or steps are supported. Bun expands them into multiple StartCalendarInterval dicts as a Cartesian product.
Viewing registered jobs:
weekly-report:
Windows
Bun uses Windows Task Scheduler with XML-based task definitions. Bun registers each job as a scheduled task namedbun-cron-<title> using CalendarTrigger elements and Repetition patterns.
Most cron expressions are fully supported, including @daily, @weekly, @monthly, @yearly, ranges (1-5), lists (1,15), named days/months, and day-of-month patterns.
User context
Bun registers tasks with theS4U (Service-for-User) logon type, which runs jobs as the registering user even when not logged in. Linux crontab behaves the same way. No password is stored.
TCP/IP networking (fetch(), HTTP, WebSocket, database connections) works normally. The only restriction is that S4U tasks cannot access Windows-authenticated network resources (SMB file shares, mapped drives, Kerberos/NTLM services).
On some headless servers and CI environments, the current user’s Security Identifier (SID) cannot be resolved, for example with service accounts created by NSSM or similar tools. In that case, Bun.cron() fails with an error explaining the issue. To work around this, either run Bun as a regular user account, or create the scheduled task manually with schtasks /create /xml <file> /tn <name> /ru SYSTEM /f.
Trigger limit
Expressions that work on all platforms:
Expressions that fail on Windows (but work on Linux and macOS):
The key factor is whether the expression can use a
Repetition interval (single trigger) or must expand to individual CalendarTrigger elements. Minute steps that evenly divide 60 (*/1, */2, */3, */4, */5, */6, */10, */12, */15, */20, */30) use Repetition and work regardless of other fields. Steps that don’t divide 60 (*/7, */8, */9, */11, */13, etc.) must be expanded, and with 24 hours active, the count quickly exceeds 48.
To work around it, simplify the expression or restrict the hour range:
Windows containers
Viewing registered jobs:bun-cron-<title>, right-click, and delete it.
Bun.cron.remove()
Remove a previously registered cron job by its title. Works on all platforms.
Bun.cron() did:
Removing a job that doesn’t exist resolves without error.