mrkeyoor.com_
Sat 08 Aug 17:40 UTC
npmInfraupdated 08 Aug 2026

croner

Croner is a zero-dependency JavaScript and TypeScript cron parser and in-process scheduler. It runs callbacks from cron expressions or one-time dates, calculates past and future occurrences, supports named time zones, and understands extensions such as seconds, years, last days, nearest weekdays, and nth weekdays. It works in Node 18+, Bun, Deno, and browsers, with controls for pausing, stopping, overlap protection, error handling, and timer unref. It does not persist jobs or coordinate them across processes.

Verdict

Croner is one of the better in-memory cron libraries: compact, carefully documented, timezone-aware, and unusually good at expression inspection. Do not use it as a substitute for a durable distributed scheduler.

API stability4/5The constructor and job controls are small and established, but version 10 requires Node 18 and migration notes matter because earlier modes and import patterns changed.
Docs5/5The README and dedicated manual document syntax, options, DST behavior, runtime support, migrations, controls, and difficult day-field semantics in useful detail.
Maintenance4/5Version 10.0.1 is current, the repository was pushed in July 2026, and only 7 issues and PRs are open, though maintenance appears concentrated around one project owner.
Ecosystem4/5It supports Node, Bun, Deno, browsers, ESM, CommonJS, npm, JSR, and CDN use with no dependencies, but intentionally does not integrate with durable queue backends.

Use it if

  • You need a small in-process scheduler with timezone and daylight-saving handling
  • You need to parse cron expressions or show upcoming run times without actually scheduling a task
  • You want overlap protection for an async callback without adding a queue system
  • Your app targets several JavaScript runtimes and you want both ESM and CommonJS entry points
Skip it if

Setup reality

Installation is genuinely small: one dependency-free package, included types, ESM and CommonJS builds, and Node 18 or newer. Production setup is harder than the import suggests. Choose an explicit timezone, add a catch handler, decide whether long tasks may overlap, stop jobs during shutdown, and ensure only one replica owns each schedule.

Patterns

Run every five secondsschedule-interval

import { Cron } from 'croner'

const job = new Cron('*/5 * * * * *', () => {
  console.log('five seconds elapsed')
})

Six fields include seconds. A conventional five-field pattern starts at minutes, so count fields before copying a schedule.

Run at a named local timeschedule-timezone

const reportJob = new Cron('0 30 8 * * MON-FRI', {
  timezone: 'America/New_York',
}, () => {
  sendDailyReport()
})

Named IANA timezones account for daylight-saving changes; a fixed utcOffset does not.

Prevent overlapping async runsprotect-async-job

const syncJob = new Cron('0 * * * * *', {
  protect: true,
  catch: error => console.error('sync failed', error),
}, async () => {
  await syncRemoteData()
})

protect skips a trigger while the previous callback is busy; it does not queue a catch-up run or retry failures.

Calculate future occurrencesinspect-next-runs

const schedule = new Cron('0 0 9 * * MON', { paused: true })
const dates = schedule.nextRuns(4)
console.log(dates.map(date => date.toISOString()))

Use paused when the object is only a parser; otherwise providing a callback later can turn it into an active timer.

Test whether a date matchesmatch-date

const mondays = new Cron('0 0 0 * * MON', { paused: true })
console.log(mondays.match('2026-08-10T00:00:00'))

Without timezone or utcOffset, matching follows the runtime's local timezone.

Schedule one run from an ISO local timeschedule-once

new Cron('2026-12-24T09:00:00', {
  timezone: 'Europe/Stockholm',
  catch: console.error,
}, () => {
  sendHolidayReminder()
})

With a timezone option, the offset-free ISO string is interpreted as local time in that zone and fires once.

Pause, resume, trigger, and stopcontrol-job

job.pause()
job.resume()
await job.trigger()
job.stop()

stop is permanent and cannot be followed by resume; create a new Cron instance if the schedule is needed again.

Stop automatically after a fixed countlimit-runs

const warmup = new Cron('*/10 * * * * *', {
  maxRuns: 3,
  catch: console.error,
}, () => refreshCache())

maxRuns counts triggers for this in-memory instance; it is not durable across process restarts.

Require day-of-month and weekdayrequire-both-day-fields

const firstMonday = new Cron('0 12 1 * +MON', {
  timezone: 'UTC',
}, () => runMonthlyTask())

Croner normally treats day-of-month and day-of-week as OR. The + prefix makes this pattern require both.

Let a Node process exit with a scheduled timerallow-process-exit

const cleanup = new Cron('@hourly', {
  unref: true,
  catch: console.error,
}, () => removeExpiredEntries())

unref is supported in Node and Deno, not browsers, and it means the process may exit before the next scheduled run.

Alternatives

PackageRegistryPick it when
node-cronnpmYou want a widely recognized Node-only API and only need conventional cron scheduling
cronnpmYou need a mature Node scheduler with CronTime and explicit timezone support
node-schedulenpmYou prefer date-based recurrence rules and one-off jobs alongside cron expressions