Skip to main content
This guide will walk you through creating your first scheduled task, from defining a simple schedule to running it in production.

Create your first schedule

1

Define a scheduled callback

Open the start/scheduler.ts file and add a simple scheduled callback:
start/scheduler.ts
import scheduler from 'adonisjs-scheduler/services/main'

scheduler.call(() => {
  console.log('Task running at:', new Date().toISOString())
}).everyMinute()
This schedule will execute the callback function every minute.
2

Run the scheduler

Start the scheduler using the Ace command:
node ace scheduler:run
You should see the message printed to the console every minute.
During development, use the --watch flag to automatically restart the scheduler when files change:
node ace scheduler:run --watch
3

Create a scheduled command

For more complex tasks, create a dedicated Ace command. Generate a new command:
node ace make:command cleanup_old_logs
Implement the command logic:
commands/cleanup_old_logs.ts
import { BaseCommand } from '@adonisjs/core/ace'
import { CommandOptions } from '@adonisjs/core/types/ace'

export default class CleanupOldLogs extends BaseCommand {
  static commandName = 'cleanup:old:logs'
  static description = 'Clean up log files older than 30 days'

  static options: CommandOptions = {
    startApp: true,
  }

  async run() {
    this.logger.info('Cleaning up old logs...')
    
    // Your cleanup logic here
    // Example: Delete logs older than 30 days
    
    this.logger.success('Cleanup completed!')
  }
}
4

Schedule the command

Add the command to your scheduler configuration:
start/scheduler.ts
import scheduler from 'adonisjs-scheduler/services/main'
import CleanupOldLogs from '#commands/cleanup_old_logs'

scheduler.command(CleanupOldLogs).daily()
The command will now run every day at midnight.
5

Schedule with arguments

If your command accepts arguments, pass them when scheduling:
commands/purge_users.ts
import { BaseCommand, args } from '@adonisjs/core/ace'
import { CommandOptions } from '@adonisjs/core/types/ace'

export default class PurgeUsers extends BaseCommand {
  static commandName = 'purge:users'
  static description = 'Purge inactive users'

  static options: CommandOptions = {
    startApp: true,
  }

  @args.string()
  declare olderThan: string

  async run() {
    this.logger.info(`Purging users older than ${this.olderThan}`)
    // Your purge logic here
  }
}
Schedule it with arguments:
start/scheduler.ts
import scheduler from 'adonisjs-scheduler/services/main'
import PurgeUsers from '#commands/purge_users'

scheduler.command(PurgeUsers, ['30 days']).weekly()

Common scheduling patterns

Here are some practical examples of scheduling tasks:
scheduler.command('send:notifications').everyMinute()

Advanced features

Prevent overlapping executions

If a task might take longer than its schedule interval, prevent overlapping executions:
start/scheduler.ts
scheduler.command('long:running:task')
  .everyFiveMinutes()
  .withoutOverlapping()
You can also specify a lock expiration time (in milliseconds):
start/scheduler.ts
scheduler.command('long:running:task')
  .everyFiveMinutes()
  .withoutOverlapping(30_000) // Lock expires after 30 seconds

Using decorators

Define schedules directly on command classes using the @schedule decorator:
commands/purge_users.ts
import { BaseCommand, args } from '@adonisjs/core/ace'
import { schedule } from 'adonisjs-scheduler'

@schedule('0 0 * * *') // Midnight daily
@schedule((s) => s.everyFiveSeconds().immediate(), ['7 days'])
export default class PurgeUsers extends BaseCommand {
  static commandName = 'purge:users'

  @args.string()
  declare olderThan: string

  async run() {
    this.logger.info(`Purging users older than ${this.olderThan}`)
  }
}

Organizing with tags

Group related schedules using tags:
start/scheduler.ts
scheduler.withTag(() => {
  scheduler.command('send:notifications').everyMinute()
  scheduler.command('process:queue').everyFiveMinutes()
}, 'background-jobs')

// Run only specific tags
// node ace scheduler:run --tag background-jobs

Lifecycle hooks

Execute code before and after scheduled tasks:
start/scheduler.ts
scheduler.command('backup:database')
  .daily()
  .before(async () => {
    console.log('Starting backup...')
  })
  .after(async () => {
    console.log('Backup completed!')
  })

Timezone support

Run tasks in specific timezones:
start/scheduler.ts
scheduler.command('send:reports')
  .dailyAt('09:00')
  .timezone('America/New_York')

Running in production

For production environments, use a process manager to keep the scheduler running:

Using PM2

ecosystem.config.json
{
  "apps": [
    {
      "name": "scheduler",
      "script": "./ace.js",
      "args": "scheduler:run",
      "instances": 1,
      "autorestart": true,
      "watch": false,
      "max_memory_restart": "1G"
    }
  ]
}
Start the scheduler:
pm2 start ecosystem.config.json

Using systemd

Create a systemd service file:
/etc/systemd/system/scheduler.service
[Unit]
Description=AdonisJS Scheduler
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/node ace.js scheduler:run
Restart=on-failure

[Install]
WantedBy=multi-user.target
Enable and start the service:
sudo systemctl enable scheduler
sudo systemctl start scheduler

Using Docker

Add a scheduler service to your docker-compose.yml:
docker-compose.yml
services:
  scheduler:
    build: .
    command: node ace scheduler:run
    restart: unless-stopped
    environment:
      - NODE_ENV=production
    depends_on:
      - database

Programmatic usage

For advanced use cases, you can manually control the scheduler worker:
import { Worker } from 'adonisjs-scheduler'
import app from '@adonisjs/core/services/app'

const worker = new Worker(app)

// Handle graceful shutdown
app.terminating(async () => {
  await worker.stop()
})

// Start the worker
await worker.start()
When using programmatic initialization, ensure the scheduler provider is enabled for your environment in adonisrc.ts.

Listing schedules

View all registered schedules:
node ace scheduler:list
This command displays all scheduled tasks with their frequencies and configurations.

Next steps

Now that you’ve created your first scheduled tasks, explore more advanced features:

Build docs developers (and LLMs) love