> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/taskforcesh/bullmq/llms.txt
> Use this file to discover all available pages before exploring further.

# Repeat Options

> Configure advanced options for repeatable jobs including start dates, end dates, limits, and more

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.

```typescript theme={null}
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' },
  },
);
```

<ParamField path="startDate" type="Date | string | number">
  Timestamp when the repeat should start. Can be a Date object, ISO string, or milliseconds.
</ParamField>

<Info>
  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.
</Info>

### endDate

Specifies when the job should stop being scheduled, effectively setting an expiration date for the job repetitions.

```typescript theme={null}
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' },
  },
);
```

<ParamField path="endDate" type="Date | string | number">
  Timestamp when the repeat should end. Can be a Date object, ISO string, or milliseconds.
</ParamField>

<Warning>
  Once the endDate is reached, no new jobs will be created. The scheduler will automatically stop producing jobs.
</Warning>

### 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.

```typescript theme={null}
await myQueue.upsertJobScheduler(
  'limited-job',
  {
    every: 10000, // every 10 seconds
    limit: 10, // limit to 10 executions
  },
  {
    name: 'limited-execution-job',
    data: { message: 'Limited runs' },
  },
);
```

<ParamField path="limit" type="number">
  Maximum number of times the job should repeat. After reaching this count, the scheduler stops.
</ParamField>

### immediately (Deprecated)

<Warning>
  From version 5.19.0 onwards, the "immediately" option has been deprecated. The current behavior is as if "immediately" was always true.
</Warning>

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.

```typescript theme={null}
// 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.

```typescript theme={null}
await myQueue.upsertJobScheduler(
  'daily-est',
  {
    pattern: '0 9 * * *', // 9 AM
    tz: 'America/New_York',
  },
  {
    name: 'morning-report',
    data: {},
  },
);
```

<ParamField path="tz" type="string">
  IANA timezone identifier (e.g., 'America/New\_York', 'Europe/London', 'Asia/Tokyo')
</ParamField>

<Note>
  Timezone support handles daylight saving time transitions automatically. Your jobs will continue to run at the correct local time regardless of DST changes.
</Note>

#### Common Timezones

```typescript theme={null}
// 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.

```typescript theme={null}
await myQueue.upsertJobScheduler(
  'offset-job',
  {
    every: 60000, // every minute
    offset: 5000, // add 5 seconds delay
  },
  {
    name: 'delayed-task',
    data: {},
  },
);
```

<ParamField path="offset" type="number">
  Offset in milliseconds to apply to the next iteration time. Can be positive (delay) or negative (advance).
</ParamField>

## Combining Options

You can combine multiple options to create sophisticated scheduling patterns:

### Example: Limited Campaign

```typescript theme={null}
// 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

```typescript theme={null}
// 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

```typescript theme={null}
// 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

<ResponseField name="pattern" type="string">
  Cron expression for scheduling
</ResponseField>

<ResponseField name="every" type="number">
  Interval in milliseconds between executions
</ResponseField>

<ResponseField name="limit" type="number">
  Maximum number of times to repeat
</ResponseField>

<ResponseField name="startDate" type="Date | string | number">
  When to start scheduling
</ResponseField>

<ResponseField name="endDate" type="Date | string | number">
  When to stop scheduling
</ResponseField>

<ResponseField name="tz" type="string">
  IANA timezone identifier for cron patterns
</ResponseField>

<ResponseField name="offset" type="number">
  Offset in milliseconds to apply to next run time
</ResponseField>

<ResponseField name="immediately" type="boolean" deprecated>
  Deprecated: First execution is now always immediate for new schedulers
</ResponseField>

## Validation Rules

<Warning>
  BullMQ enforces several validation rules when creating job schedulers:
</Warning>

1. **Cannot use both `pattern` and `every`**
   ```typescript theme={null}
   // ❌ Invalid
   await queue.upsertJobScheduler('id', {
     pattern: '0 * * * *',
     every: 60000,
   });
   ```

2. **Must specify either `pattern` or `every`**
   ```typescript theme={null}
   // ❌ Invalid
   await queue.upsertJobScheduler('id', {});
   ```

3. **Cannot use both `immediately` and `startDate`** (pre-v5.19.0)
   ```typescript theme={null}
   // ❌ Invalid
   await queue.upsertJobScheduler('id', {
     every: 60000,
     immediately: true,
     startDate: new Date('2024-01-01'),
   });
   ```

## Best Practices

<Steps>
  <Step title="Always specify timezone for cron patterns">
    Avoid ambiguity and DST issues:

    ```typescript theme={null}
    await queue.upsertJobScheduler('id', {
      pattern: '0 9 * * *',
      tz: 'America/New_York', // Clear and explicit
    });
    ```
  </Step>

  <Step title="Use endDate or limit for temporary schedulers">
    Prevent forgotten schedulers from running indefinitely:

    ```typescript theme={null}
    await queue.upsertJobScheduler('id', {
      every: 60000,
      endDate: new Date('2024-12-31'), // Auto-cleanup
    });
    ```
  </Step>

  <Step title="Consider offset for load distribution">
    Stagger jobs to avoid resource spikes:

    ```typescript theme={null}
    await queue.upsertJobScheduler('scheduler-1', {
      every: 60000,
      offset: 0,
    });

    await queue.upsertJobScheduler('scheduler-2', {
      every: 60000,
      offset: 20000, // 20 seconds later
    });
    ```
  </Step>

  <Step title="Test with startDate in development">
    Validate scheduling logic without waiting:

    ```typescript theme={null}
    await queue.upsertJobScheduler('test-scheduler', {
      pattern: '0 9 * * *',
      startDate: new Date(Date.now() + 5000), // 5 seconds from now
      limit: 3, // Only run 3 times
    });
    ```
  </Step>
</Steps>

## Related Topics

<CardGroup cols={2}>
  <Card title="Repeat Strategies" icon="clock-rotate-left" href="/schedulers/repeat-strategies">
    Learn about every, cron, and custom strategies
  </Card>

  <Card title="Overview" icon="book" href="/schedulers/overview">
    Understand Job Schedulers basics
  </Card>

  <Card title="Management" icon="list-check" href="/schedulers/management">
    Manage and monitor schedulers
  </Card>

  <Card title="RepeatableOptions" icon="code" href="/api/interfaces/repeatable-options">
    Full API reference
  </Card>
</CardGroup>

## API Reference

* [Queue.upsertJobScheduler API Reference](https://api.docs.bullmq.io/classes/v5.Queue.html#upsertjobscheduler)
* [RepeatOptions Interface](https://api.docs.bullmq.io/interfaces/v5.RepeatOptions.html)
