Skip to main content

Overview

The cron() function schedules recurring tasks using cron expressions. Perfect for automated messages, cleanups, reminders, and periodic maintenance.

Signature

function cron(
  name: string,
  cronExpr: string,
  handler: (ctx: CronContext) => void | Promise<void>,
  options?: CronOptions
): void

Parameters

name
string
required
Unique identifier for this cron job
cronExpr
string
required
Cron expression defining when the job runs (see Cron Syntax)
handler
function
required
Function to execute on scheduleReceives CronContext with:
  • name - The cron job name
  • scheduledAt - ISO8601 timestamp of when execution was scheduled
options
CronOptions
Optional configuration

Return Value

Returns void. The cron job is registered immediately.

Cron Syntax

Cron expressions use 5 fields:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
│ │ │ │ │
* * * * *

Special Characters

  • * - Any value
  • , - Value list separator (e.g., 1,15 = 1st and 15th)
  • - - Range (e.g., 1-5 = 1 through 5)
  • / - Step values (e.g., */15 = every 15 units)

Common Patterns

ExpressionDescription
* * * * *Every minute
0 * * * *Every hour (at minute 0)
0 0 * * *Every day at midnight
0 0 * * 0Every Sunday at midnight
0 9 * * 1-5Every weekday at 9 AM
*/15 * * * *Every 15 minutes
0 */6 * * *Every 6 hours
0 0 1 * *First day of every month
0 0 1 1 *Every January 1st

Examples

Daily Reminder

cron('daily-reminder', '0 9 * * *', async (ctx) => {
  const channel = '123456789' // Your channel ID
  await rest.sendMessage({
    channelId: channel,
    content: 'Good morning! Don\'t forget to check the announcements.'
  })
})

Hourly Cleanup

cron('cleanup', '0 * * * *', async (ctx) => {
  const db = kv.store('cache')
  
  // List all temporary keys
  const result = await db.list({ prefix: 'temp:' })
  
  // Delete expired entries
  for (const key of result.keys) {
    await db.delete(key.name)
  }
  
  console.log(`Cleaned up ${result.keys.length} temporary entries at ${ctx.scheduledAt}`)
})

Weekly Stats Report

cron('weekly-report', '0 18 * * 5', async (ctx) => {
  const statsChannel = '987654321'
  const points = kv.store('points')
  
  const allUsers = await points.list({ prefix: 'user:' })
  const topUsers = allUsers.keys
    .sort((a, b) => parseInt(b.name) - parseInt(a.name))
    .slice(0, 10)
  
  const reportEmbed = embed()
    .setTitle('📊 Weekly Leaderboard')
    .setDescription('Top 10 users this week')
    .setColor(0x00FF00)
    .setTimestamp(ctx.scheduledAt)
    .toJSON()
  
  await rest.sendMessage({
    channelId: statsChannel,
    embeds: [reportEmbed]
  })
}, { skipIfRunning: true })

Every 15 Minutes Check

cron('status-check', '*/15 * * * *', async (ctx) => {
  // Runs at :00, :15, :30, :45 of every hour
  console.log('Running status check at', ctx.scheduledAt)
  
  // Your monitoring logic here
})

Multiple Jobs

// Backup every 6 hours
cron('backup', '0 */6 * * *', async (ctx) => {
  console.log('Creating backup...')
  // Backup logic
})

// Clear cache every hour
cron('clear-cache', '0 * * * *', async (ctx) => {
  console.log('Clearing cache...')
  // Cache clearing logic
})

// Send daily digest at 6 PM
cron('daily-digest', '0 18 * * *', async (ctx) => {
  console.log('Sending daily digest...')
  // Digest logic
})

Using skipIfRunning

// Long-running task that shouldn't overlap
cron('data-sync', '*/5 * * * *', async (ctx) => {
  console.log('Starting data sync...')
  
  // Simulate long operation
  await new Promise(resolve => setTimeout(resolve, 7 * 60 * 1000)) // 7 minutes
  
  console.log('Data sync complete')
}, { skipIfRunning: true })

// If sync takes >5 minutes, the next scheduled run will be skipped

Context Properties

name
string
The name of the cron job (same as first parameter)
scheduledAt
string
ISO8601 timestamp of when this execution was scheduled

Best Practices

Use Unique Names

// Good - unique, descriptive names
cron('daily-welcome', '0 9 * * *', handler)
cron('hourly-cleanup', '0 * * * *', handler)

// Avoid - generic names that might conflict
cron('task', '0 * * * *', handler)

Handle Errors

cron('risky-task', '0 0 * * *', async (ctx) => {
  try {
    await performRiskyOperation()
  } catch (error) {
    console.error('Cron job failed:', error)
    // Optional: send alert to admin channel
  }
})

Use skipIfRunning for Long Tasks

cron('long-running', '*/10 * * * *', async (ctx) => {
  // Task that might take >10 minutes
  await heavyOperation()
}, { skipIfRunning: true })

Log Execution

cron('important-task', '0 */6 * * *', async (ctx) => {
  console.log(`[${ctx.name}] Starting at ${ctx.scheduledAt}`)
  
  await performTask()
  
  console.log(`[${ctx.name}] Completed successfully`)
})

Notes

  • Cron jobs are registered per guild
  • Times are in UTC (not local timezone)
  • Minimum interval is 1 minute
  • Handler can be async or sync
  • Multiple handlers with the same name will all execute
  • Use skipIfRunning: true for tasks that might take longer than their interval
  • Cron expressions are validated when cron() is called

Timezone Considerations

All cron times are in UTC. To schedule for a specific timezone:
// 9 AM EST = 2 PM UTC (EST is UTC-5, or UTC-4 during DST)
cron('est-morning', '0 14 * * *', handler)

// 6 PM PST = 2 AM UTC next day (PST is UTC-8, or UTC-7 during PDT)
cron('pst-evening', '0 2 * * *', handler)
Consider using a library to calculate UTC offsets if needed.

Build docs developers (and LLMs) love