Skip to main content
In addition to defining schedules in your start/scheduler.ts file, you can use the @schedule decorator to define schedules directly on your command classes. This approach keeps scheduling configuration close to the command implementation.

Basic usage

Import the @schedule decorator from the scheduler package and apply it to your command class:
import { BaseCommand, args } from '@adonisjs/core/ace'
import { schedule } from 'adonisjs-scheduler'

@schedule('* * * * *')
export default class PurgeUsers extends BaseCommand {
  static commandName = 'purge:users'
  static description = 'Purge old user records'

  async run() {
    // Command implementation
  }
}
The decorator accepts a cron expression as its first parameter, following the extended format with seconds:
seconds minutes hours dayOfMonth month dayOfWeek

Passing command arguments

If your command requires arguments, pass them as the second parameter to the decorator:
import { BaseCommand, args } from '@adonisjs/core/ace'
import { schedule } from 'adonisjs-scheduler'

@schedule('* * * * *', ['30 days'])
export default class PurgeUsers extends BaseCommand {
  static commandName = 'purge:users'
  static description = 'Purge old user records'

  @args.string()
  declare olderThan: string

  async run() {
    this.logger.info(`Purging users older than ${this.olderThan}`)
    // Command implementation
  }
}
The second parameter accepts either a single string or an array of strings.

Using the fluent API

Instead of a cron expression, you can pass a callback function that receives a schedule instance. This allows you to use the fluent frequency API:
import { BaseCommand, args } from '@adonisjs/core/ace'
import { schedule } from 'adonisjs-scheduler'

@schedule((s) => s.everyFiveSeconds())
export default class PurgeUsers extends BaseCommand {
  static commandName = 'purge:users'
  static description = 'Purge old user records'

  async run() {
    // Command implementation
  }
}
The callback receives a ScheduleCommand instance that you can chain with any frequency method:
@schedule((s) => s.everyMinute().timezone('America/New_York'))
@schedule((s) => s.daily().withoutOverlapping())
@schedule((s) => s.hourly().immediate())

Combining with command arguments

When using the fluent API, you can still pass command arguments as the second parameter:
import { BaseCommand, args } from '@adonisjs/core/ace'
import { schedule } from 'adonisjs-scheduler'

@schedule((s) => s.everyFiveSeconds(), ['30 days'])
export default class PurgeUsers extends BaseCommand {
  static commandName = 'purge:users'
  static description = 'Purge old user records'

  @args.string()
  declare olderThan: string

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

Multiple schedules

You can apply the @schedule decorator multiple times to run the same command on different schedules:
import { BaseCommand, args } from '@adonisjs/core/ace'
import { schedule } from 'adonisjs-scheduler'

@schedule('* * * * *', ['30 days'])
@schedule((s) => s.everyFiveSeconds().immediate(), ['7 days'])
@schedule((s) => s.everyMinute(), ['42 days'])
export default class PurgeUsers extends BaseCommand {
  static commandName = 'purge:users'
  static description = 'Purge old user records'

  @args.string()
  declare olderThan: string

  async run() {
    this.logger.info(`Purging users older than ${this.olderThan}`)
    // Command implementation
  }
}
In this example, the purge:users command will run:
  • Every minute with the argument '30 days'
  • Every five seconds (and immediately on startup) with the argument '7 days'
  • Every minute with the argument '42 days'
Each decorator creates a separate scheduled task with its own configuration.

Decorator vs file-based scheduling

Both approaches are valid and can be used together in the same application: Decorator approach:
  • Schedules are defined close to the command implementation
  • Useful for commands that are always scheduled
  • Makes the command’s scheduling behavior immediately visible
File-based approach:
  • Schedules are centralized in start/scheduler.ts
  • Provides a single place to view all scheduled tasks
  • Easier to modify schedules without touching command files
  • Better for commands that may or may not be scheduled depending on environment
Choose the approach that best fits your project’s organization and team preferences.

Build docs developers (and LLMs) love