Job Schedulers support several options to control various aspects of job repetitions. These options work with all repeat strategies (every, cron, and custom).
Available Options
startDate
Sets a future date from which the job will start being scheduled. This is useful for setting up jobs that should begin repeating on a specific day.
import { Queue } from 'bullmq' ;
const connection = { host: 'localhost' , port: 6379 };
const myQueue = new Queue ( 'my-dated-jobs' , { connection });
await myQueue . upsertJobScheduler (
'start-later-job' ,
{
every: 60000 , // every minute
startDate: new Date ( '2024-10-15T00:00:00Z' ), // start on October 15, 2024
},
{
name: 'timed-start-job' ,
data: { message: 'Starting later' },
},
);
Timestamp when the repeat should start. Can be a Date object, ISO string, or milliseconds.
Jobs will not be scheduled before the startDate. If you create a scheduler with a startDate in the future, the first job will be created at that time.
endDate
Specifies when the job should stop being scheduled, effectively setting an expiration date for the job repetitions.
await myQueue . upsertJobScheduler (
'end-soon-job' ,
{
every: 60000 , // every minute
endDate: new Date ( '2024-11-01T00:00:00Z' ), // end on November 1, 2024
},
{
name: 'timed-end-job' ,
data: { message: 'Ending soon' },
},
);
Timestamp when the repeat should end. Can be a Date object, ISO string, or milliseconds.
Once the endDate is reached, no new jobs will be created. The scheduler will automatically stop producing jobs.
limit
Limits the number of times a job will be repeated. When the count reaches this limit, no more jobs will be produced for the given job scheduler.
await myQueue . upsertJobScheduler (
'limited-job' ,
{
every: 10000 , // every 10 seconds
limit: 10 , // limit to 10 executions
},
{
name: 'limited-execution-job' ,
data: { message: 'Limited runs' },
},
);
Maximum number of times the job should repeat. After reaching this count, the scheduler stops.
From version 5.19.0 onwards, the “immediately” option has been deprecated. The current behavior is as if “immediately” was always true.
Previously, this setting forced the job to execute as soon as it was added, regardless of the schedule.
Historical Context
When using the every option, jobs are scheduled based on fixed time intervals aligned with the clock. For instance, with an interval of 2000ms, jobs trigger at every even second—0, 2, 4, 6, 8 seconds, and so on.
Before v5.19.0, if you wanted a job to begin processing immediately after adding a job scheduler (rather than waiting for the next aligned interval), you would use immediately: true.
Current Behavior (v5.19.0+)
Now, the first repetition always executes immediately when you create a new job scheduler, then repeats according to the every or pattern setting. Subsequent calls to upsertJobScheduler for existing schedulers will not lead to immediate repetitions and will instead follow the configured interval.
// First call: Job executes immediately, then every day
await myQueue . upsertJobScheduler (
'immediate-job' ,
{
every: 86400000 , // once a day
},
{
name: 'instant-job' ,
data: { message: 'Immediate start' },
},
);
// Subsequent update: No immediate execution, follows existing schedule
await myQueue . upsertJobScheduler (
'immediate-job' ,
{
every: 86400000 ,
},
{
name: 'instant-job' ,
data: { message: 'Updated data' },
},
);
tz (Timezone)
Specifies the timezone for cron pattern scheduling. This is crucial for jobs that need to run at specific local times.
await myQueue . upsertJobScheduler (
'daily-est' ,
{
pattern: '0 9 * * *' , // 9 AM
tz: 'America/New_York' ,
},
{
name: 'morning-report' ,
data: {},
},
);
IANA timezone identifier (e.g., ‘America/New_York’, ‘Europe/London’, ‘Asia/Tokyo’)
Timezone support handles daylight saving time transitions automatically. Your jobs will continue to run at the correct local time regardless of DST changes.
Common Timezones
// North America
'America/New_York' // Eastern Time
'America/Chicago' // Central Time
'America/Denver' // Mountain Time
'America/Los_Angeles' // Pacific Time
// Europe
'Europe/London' // GMT/BST
'Europe/Paris' // CET/CEST
'Europe/Berlin' // CET/CEST
// Asia
'Asia/Tokyo' // JST
'Asia/Shanghai' // CST
'Asia/Dubai' // GST
// Other
'Australia/Sydney' // AEST/AEDT
'UTC' // Coordinated Universal Time
offset
Applies an offset in milliseconds to the calculated next run time. This is useful for staggering job execution or adding small delays.
await myQueue . upsertJobScheduler (
'offset-job' ,
{
every: 60000 , // every minute
offset: 5000 , // add 5 seconds delay
},
{
name: 'delayed-task' ,
data: {},
},
);
Offset in milliseconds to apply to the next iteration time. Can be positive (delay) or negative (advance).
Combining Options
You can combine multiple options to create sophisticated scheduling patterns:
Example: Limited Campaign
// Run daily reports for 30 days
await myQueue . upsertJobScheduler (
'month-campaign' ,
{
pattern: '0 9 * * *' , // Every day at 9 AM
tz: 'America/New_York' , // Eastern Time
startDate: new Date ( '2024-01-01' ),
endDate: new Date ( '2024-01-31' ),
limit: 30 , // Maximum 30 executions
},
{
name: 'daily-campaign-report' ,
data: { campaignId: 'jan-2024' },
},
);
Example: Scheduled Maintenance
// Weekly maintenance starting next month
await myQueue . upsertJobScheduler (
'weekly-maintenance' ,
{
pattern: '0 2 * * 0' , // Sundays at 2 AM
tz: 'UTC' ,
startDate: new Date ( '2024-02-01' ),
offset: 300000 , // 5-minute delay
},
{
name: 'system-maintenance' ,
data: { type: 'full' },
opts: {
attempts: 3 ,
backoff: {
type: 'exponential' ,
delay: 60000 ,
},
},
},
);
Example: Trial Period
// Send reminders every 3 days for 30 days
await myQueue . upsertJobScheduler (
'trial-reminders' ,
{
every: 3 * 24 * 60 * 60 * 1000 , // Every 3 days
startDate: new Date (), // Start now
limit: 10 , // 10 reminders total
},
{
name: 'trial-reminder' ,
data: { userId: 'user123' },
},
);
Complete Options Reference
Cron expression for scheduling
Interval in milliseconds between executions
Maximum number of times to repeat
IANA timezone identifier for cron patterns
Offset in milliseconds to apply to next run time
Deprecated: First execution is now always immediate for new schedulers
Validation Rules
BullMQ enforces several validation rules when creating job schedulers:
Cannot use both pattern and every
// ❌ Invalid
await queue . upsertJobScheduler ( 'id' , {
pattern: '0 * * * *' ,
every: 60000 ,
});
Must specify either pattern or every
// ❌ Invalid
await queue . upsertJobScheduler ( 'id' , {});
Cannot use both immediately and startDate (pre-v5.19.0)
// ❌ Invalid
await queue . upsertJobScheduler ( 'id' , {
every: 60000 ,
immediately: true ,
startDate: new Date ( '2024-01-01' ),
});
Best Practices
Always specify timezone for cron patterns
Avoid ambiguity and DST issues: await queue . upsertJobScheduler ( 'id' , {
pattern: '0 9 * * *' ,
tz: 'America/New_York' , // Clear and explicit
});
Use endDate or limit for temporary schedulers
Prevent forgotten schedulers from running indefinitely: await queue . upsertJobScheduler ( 'id' , {
every: 60000 ,
endDate: new Date ( '2024-12-31' ), // Auto-cleanup
});
Consider offset for load distribution
Stagger jobs to avoid resource spikes: await queue . upsertJobScheduler ( 'scheduler-1' , {
every: 60000 ,
offset: 0 ,
});
await queue . upsertJobScheduler ( 'scheduler-2' , {
every: 60000 ,
offset: 20000 , // 20 seconds later
});
Test with startDate in development
Validate scheduling logic without waiting: await queue . upsertJobScheduler ( 'test-scheduler' , {
pattern: '0 9 * * *' ,
startDate: new Date ( Date . now () + 5000 ), // 5 seconds from now
limit: 3 , // Only run 3 times
});
Repeat Strategies Learn about every, cron, and custom strategies
Overview Understand Job Schedulers basics
Management Manage and monitor schedulers
RepeatableOptions Full API reference
API Reference