450 lines
18 KiB
Plaintext
450 lines
18 KiB
Plaintext
---
|
|
title: "Scheduled tasks (cron)"
|
|
description: "A task that is triggered on a recurring schedule using cron syntax."
|
|
---
|
|
|
|
<Note>
|
|
Scheduled tasks are only for recurring tasks. If you want to trigger a one-off task at a future
|
|
time, you should [use the delay option](/triggering#delay).
|
|
</Note>
|
|
|
|
## Defining a scheduled task
|
|
|
|
This task will run when any of the attached schedules trigger. They have a predefined payload with some useful properties:
|
|
|
|
```ts
|
|
import { schedules } from "@trigger.dev/sdk";
|
|
|
|
export const firstScheduledTask = schedules.task({
|
|
id: "first-scheduled-task",
|
|
run: async (payload) => {
|
|
//when the task was scheduled to run
|
|
//note this will be slightly different from new Date() because it takes a few ms to run the task
|
|
console.log(payload.timestamp); //is a Date object
|
|
|
|
//when the task was last run
|
|
//this can be undefined if it's never been run
|
|
console.log(payload.lastTimestamp); //is a Date object or undefined
|
|
|
|
//the timezone the schedule was registered with, defaults to "UTC"
|
|
//this is in IANA format, e.g. "America/New_York"
|
|
//See the full list here: https://cloud.trigger.dev/timezones
|
|
console.log(payload.timezone); //is a string
|
|
|
|
//If you want to output the time in the user's timezone do this:
|
|
const formatted = payload.timestamp.toLocaleString("en-US", {
|
|
timeZone: payload.timezone,
|
|
});
|
|
|
|
//the schedule id (you can have many schedules for the same task)
|
|
//using this you can remove the schedule, update it, etc
|
|
console.log(payload.scheduleId); //is a string
|
|
|
|
//you can optionally provide an external id when creating the schedule
|
|
//usually you would set this to a userId or some other unique identifier
|
|
//this can be undefined if you didn't provide one
|
|
console.log(payload.externalId); //is a string or undefined
|
|
|
|
//the next 5 dates this task is scheduled to run
|
|
console.log(payload.upcoming); //is an array of Date objects
|
|
},
|
|
});
|
|
```
|
|
|
|
You can see from the comments that the payload has several useful properties:
|
|
|
|
- `timestamp` - the time the task was scheduled to run, as a UTC date.
|
|
- `lastTimestamp` - the time the task was last run, as a UTC date.
|
|
- `timezone` - the timezone the schedule was registered with, defaults to "UTC". In IANA format, e.g. "America/New_York".
|
|
- `scheduleId` - the id of the schedule that triggered the task
|
|
- `externalId` - the external id you (optionally) provided when creating the schedule
|
|
- `upcoming` - the next 5 times the task is scheduled to run
|
|
|
|
<Note>
|
|
This task will NOT get triggered on a schedule until you attach a schedule to it. Read on for how
|
|
to do that.
|
|
</Note>
|
|
|
|
Like all tasks they don't have timeouts, they should be placed inside a [/trigger folder](/config/config-file), and you [can configure them](/tasks/overview#defining-a-task).
|
|
|
|
You can set a [TTL](/runs#time-to-live-ttl) on a scheduled task to automatically expire runs that aren't dequeued in time. This is useful when a schedule fires while the previous run is still executing - rather than queueing up stale runs, they'll expire:
|
|
|
|
```ts
|
|
export const frequentTask = schedules.task({
|
|
id: "frequent-task",
|
|
ttl: "5m",
|
|
run: async (payload) => {
|
|
//...
|
|
},
|
|
});
|
|
```
|
|
|
|
## How to attach a schedule
|
|
|
|
Now that we've defined a scheduled task, we need to define when it will actually run. To do this we need to attach one or more schedules.
|
|
|
|
There are two ways of doing this:
|
|
|
|
- **Declarative:** defined on your `schedules.task`. They sync when you run the dev command or deploy.
|
|
- **Imperative:** created from the dashboard or by using the imperative SDK functions like `schedules.create()`.
|
|
|
|
<Info>
|
|
A scheduled task can have multiple schedules attached to it, including a declarative schedule
|
|
and/or many imperative schedules.
|
|
</Info>
|
|
|
|
### Declarative schedules
|
|
|
|
These sync when you run the [dev](/cli-dev-commands) or [deploy](/cli-deploy-commands) commands.
|
|
|
|
To create them you add the `cron` property to your `schedules.task()`. This property is optional and is only used if you want to add a declarative schedule to your task:
|
|
|
|
```ts
|
|
export const firstScheduledTask = schedules.task({
|
|
id: "first-scheduled-task",
|
|
//every two hours (UTC timezone)
|
|
cron: "0 */2 * * *",
|
|
run: async (payload, { ctx }) => {
|
|
//do something
|
|
},
|
|
});
|
|
```
|
|
|
|
If you use a string it will be in UTC. Alternatively, you can specify a timezone like this:
|
|
|
|
```ts
|
|
export const secondScheduledTask = schedules.task({
|
|
id: "second-scheduled-task",
|
|
cron: {
|
|
//5am every day Tokyo time
|
|
pattern: "0 5 * * *",
|
|
timezone: "Asia/Tokyo",
|
|
//optional, defaults to all environments
|
|
//possible values are "PRODUCTION", "STAGING", "PREVIEW" and "DEVELOPMENT"
|
|
environments: ["PRODUCTION", "STAGING"],
|
|
},
|
|
run: async (payload) => {},
|
|
});
|
|
```
|
|
|
|
When you run the [dev](/cli-dev-commands) or [deploy](/cli-deploy-commands) commands, declarative schedules will be synced. If you add, delete or edit the `cron` property it will be updated when you run these commands. You can view your synced schedules in the dashboard: open the task on the Tasks page and check its "Schedules" tab.
|
|
|
|
### Imperative schedules
|
|
|
|
Alternatively you can explicitly attach schedules to a `schedules.task`. You can do this in the dashboard from the scheduled task's page by pressing the "Create schedule" button, or you can use the SDK to create schedules.
|
|
|
|
The advantage of imperative schedules is that they can be created dynamically, for example, you could create a schedule for each user in your database. They can also be activated, disabled, edited, and deleted without deploying new code by using the SDK or dashboard.
|
|
|
|
To use imperative schedules you need to do two things:
|
|
|
|
1. Define a task in your code using `schedules.task()`.
|
|
2. Attach 1+ schedules to the task either using the dashboard or the SDK.
|
|
|
|
## Spreading runs with windows
|
|
|
|
By default a schedule runs at its exact cron time. When many schedules share the same cron pattern, such as a daily `0 9 * * *`, they all fire at the same time and load your downstream systems as well as ours. A **window** spreads those runs out: each schedule is assigned a stable time within the window after its cron time, so the load is smoothed while each schedule keeps firing at a predictable, repeatable moment.
|
|
|
|
The assigned time is deterministic. A given schedule always lands at the same offset for a given interval, so runs don't jump around between occurrences. And you can always see exactly when the next run will start.
|
|
|
|
Set a `window` as either:
|
|
|
|
- **An absolute duration** in whole minutes or hours, up to 24 hours: `"30m"`, `"2h"`, `"24h"`. Absolute windows are capped at the next cron time, so a run is never delayed past its following occurrence.
|
|
- **A percentage** of the interval between runs: `"30%"`, `"100%"`. A `"50%"` window on an hourly schedule spreads runs across the first 30 minutes of each hour.
|
|
|
|
Set `"0m"` (or `"0%"`) for no spreading — the run fires at its exact cron time.
|
|
|
|
Declarative schedules set the window on the `cron` object:
|
|
|
|
```ts
|
|
export const dailyReport = schedules.task({
|
|
id: "daily-report",
|
|
cron: {
|
|
pattern: "0 0 * * *",
|
|
// spread this run across the 30 minutes after midnight
|
|
window: "30m",
|
|
},
|
|
run: async (payload) => {},
|
|
});
|
|
```
|
|
|
|
Imperative schedules set it when creating or updating a schedule, either in the dashboard form or through the SDK:
|
|
|
|
```ts
|
|
const createdSchedule = await schedules.create({
|
|
task: dailyReport.id,
|
|
cron: "0 0 * * *",
|
|
window: "30m",
|
|
deduplicationKey: "user_123456-daily-report",
|
|
});
|
|
```
|
|
|
|
<Note>
|
|
The payload `timestamp` and `upcoming` values are always the nominal cron times, not the assigned
|
|
times. When you retrieve a schedule, `nextRun` is the nominal cron time and `nextRunEffectiveAt`
|
|
is the assigned time the run will actually start.
|
|
</Note>
|
|
|
|
## Supported cron syntax
|
|
|
|
```
|
|
* * * * *
|
|
┬ ┬ ┬ ┬ ┬
|
|
│ │ │ │ |
|
|
│ │ │ │ └ day of week (0 - 7, 1L - 7L) (0 or 7 is Sun)
|
|
│ │ │ └───── month (1 - 12)
|
|
│ │ └────────── day of month (1 - 31, L)
|
|
│ └─────────────── hour (0 - 23)
|
|
└──────────────────── minute (0 - 59)
|
|
```
|
|
|
|
"L" means the last. In the "day of week" field, 1L means the last Monday of the month. In the "day of month" field, L means the last day of the month.
|
|
|
|
We do not support seconds in the cron syntax.
|
|
|
|
## When schedules won't trigger
|
|
|
|
There are two situations when a scheduled task won't trigger:
|
|
|
|
- For Dev environments scheduled tasks will only trigger if you're running the dev CLI.
|
|
- For Staging/Production environments scheduled tasks will only trigger if the task is in the current deployment (latest version). We won't trigger tasks from previous deployments.
|
|
|
|
## Attaching schedules in the dashboard
|
|
|
|
You need to attach a schedule to a task before it will run on a schedule. You can attach imperative schedules in the dashboard:
|
|
|
|
<Note>
|
|
**The Schedules page has moved.** There is no longer a standalone "Schedules" page in the
|
|
sidebar. Schedules now live on the Tasks page: open a scheduled task to create, view, edit,
|
|
enable/disable, and delete its schedules. The old `/schedules` URL redirects to the Tasks page.
|
|
|
|
The scheduled task must already exist first — define it in your code with `schedules.task()` and
|
|
sync it to the environment by running the [dev](/cli-dev-commands) or
|
|
[deploy](/cli-deploy-commands) command so it appears on the Tasks page. A project with no tasks
|
|
yet will only show the deploy onboarding.
|
|
</Note>
|
|
|
|
<Steps>
|
|
|
|
<Step title="Open the scheduled task">
|
|
In the sidebar select the "Tasks" page, then select the scheduled task you want to attach a
|
|
schedule to (scheduled tasks have a clock icon, and you can filter the list to Scheduled).
|
|

|
|
</Step>
|
|
|
|
<Step title="Create your schedule">
|
|
Press the "Create schedule" button, fill in the form, and press "Create schedule" when you're
|
|
done. 
|
|
|
|
These are the options when creating a schedule:
|
|
|
|
| Name | Description |
|
|
| ----------------- | --------------------------------------------------------------------------------------------- |
|
|
| Task | The id of the task you want to attach to. |
|
|
| Cron pattern | The schedule in cron format. You can also describe it in natural language and press "Generate" to fill this in. |
|
|
| Timezone | The timezone the schedule will run in. Defaults to "UTC" |
|
|
| Window | An optional [window](#spreading-runs-with-windows) to spread runs after their cron time, e.g. `30m`, `2h`, or `50%`. |
|
|
| External id | An optional external id, usually you'd use a userId. |
|
|
| Deduplication key | An optional deduplication key. If you pass the same value, it will update rather than create. Scoped per project, not per environment. |
|
|
| Environments | The environments this schedule will run in. |
|
|
|
|
</Step>
|
|
|
|
</Steps>
|
|
|
|
## Managing schedules in the dashboard
|
|
|
|
Open the scheduled task and switch to the "Schedules" tab to see every schedule attached to it — both declarative and imperative — with its type, cron pattern, external id, next and last run, and status.
|
|
|
|
Click a schedule to open the inspector, where you can **enable/disable**, **edit**, or **delete** imperative schedules without deploying new code. Declarative schedules are managed in your code, so they can't be edited or deleted from here.
|
|
|
|
## Attaching schedules with the SDK
|
|
|
|
You call `schedules.create()` to create a schedule from your code. Here's the simplest possible example:
|
|
|
|
```ts
|
|
const createdSchedule = await schedules.create({
|
|
//The id of the scheduled task you want to attach to.
|
|
task: firstScheduledTask.id,
|
|
//The schedule in cron format.
|
|
cron: "0 0 * * *",
|
|
//this is required, it prevents you from creating duplicate schedules. It will update the schedule if it already exists.
|
|
deduplicationKey: "my-deduplication-key",
|
|
});
|
|
```
|
|
|
|
<Note>The `task` id must be a task that you defined using `schedules.task()`.</Note>
|
|
|
|
You can create many schedules with the same `task`, `cron`, and `externalId` but only one with the same `deduplicationKey`.
|
|
|
|
<Note>
|
|
The deduplication key is **per project**, not per environment. Using the same key in Production and Staging creates a single schedule; the last create/update decides which environment it appears in. For fixed schedules, prefer **declarative** (cron on the task). If using imperative across environments, use a different deduplication key per environment (e.g. include the env name in the key).
|
|
</Note>
|
|
|
|
This means you can have thousands of schedules attached to a single task, but only one schedule per `deduplicationKey`. Here's an example with all the options:
|
|
|
|
```ts
|
|
const createdSchedule = await schedules.create({
|
|
//The id of the scheduled task you want to attach to.
|
|
task: firstScheduledTask.id,
|
|
//The schedule in cron format.
|
|
cron: "0 0 * * *",
|
|
// Optional, it defaults to "UTC". In IANA format, e.g. "America/New_York".
|
|
// In this case, the task will run at midnight every day in New York time.
|
|
// If you specify a timezone it will automatically work with daylight saving time.
|
|
timezone: "America/New_York",
|
|
//Optionally, you can specify your own IDs (like a user ID) and then use it inside the run function of your task.
|
|
//This allows you to have per-user cron tasks.
|
|
externalId: "user_123456",
|
|
//You can only create one schedule with this key.
|
|
//If you use it twice, the second call will update the schedule.
|
|
//This is useful because you don't want to create duplicate schedules for a user.
|
|
deduplicationKey: "user_123456-todo_reminder",
|
|
});
|
|
```
|
|
|
|
See [the SDK reference](/management/schedules/create) for full details.
|
|
|
|
### Dynamic schedules (or multi-tenant schedules)
|
|
|
|
By using the `externalId` you can have schedules for your users. This is useful for things like reminders, where you want to have a schedule for each user.
|
|
|
|
A reminder task:
|
|
|
|
```ts /trigger/reminder.ts
|
|
import { schedules } from "@trigger.dev/sdk";
|
|
|
|
//this task will run when any of the attached schedules trigger
|
|
export const reminderTask = schedules.task({
|
|
id: "todo-reminder",
|
|
run: async (payload) => {
|
|
if (!payload.externalId) {
|
|
throw new Error("externalId is required");
|
|
}
|
|
|
|
//get user using the externalId you used when creating the schedule
|
|
const user = await db.getUser(payload.externalId);
|
|
|
|
//send a reminder email
|
|
await sendReminderEmail(user);
|
|
},
|
|
});
|
|
```
|
|
|
|
Then in your backend code, you can create a schedule for each user:
|
|
|
|
```ts Next.js API route
|
|
import { reminderTask } from "~/trigger/reminder";
|
|
|
|
//app/reminders/route.ts
|
|
export async function POST(request: Request) {
|
|
//get the JSON from the request
|
|
const data = await request.json();
|
|
|
|
//create a schedule for the user
|
|
const createdSchedule = await schedules.create({
|
|
task: reminderTask.id,
|
|
//8am every day
|
|
cron: "0 8 * * *",
|
|
//the user's timezone
|
|
timezone: data.timezone,
|
|
//the user id
|
|
externalId: data.userId,
|
|
//this makes it impossible to have two reminder schedules for the same user
|
|
deduplicationKey: `${data.userId}-reminder`,
|
|
});
|
|
|
|
//return a success response with the schedule
|
|
return Response.json(createdSchedule);
|
|
}
|
|
```
|
|
|
|
You can also retrieve, list, delete, deactivate and re-activate schedules using the SDK. More on that later.
|
|
|
|
## Testing schedules
|
|
|
|
You can test a scheduled task in the dashboard. Note that the `scheduleId` will always come through as `sched_1234` to the run.
|
|
|
|
<Note>
|
|
There is no longer a standalone "Test" page in the sidebar. You test a task from the task itself —
|
|
open it on the Tasks page and press the "Test schedule" button.
|
|
</Note>
|
|
|
|
<Steps>
|
|
|
|
<Step title="Open the test page for your task">
|
|
On the "Tasks" page, open your scheduled task and press the "Test schedule" button.
|
|

|
|
</Step>
|
|
|
|
<Step title="Run the test">
|
|
Fill in the form [1]. You can select from a recent run [2] to pre-populate the fields. Press "Run
|
|
test" when you're ready 
|
|
</Step>
|
|
|
|
</Steps>
|
|
|
|
## Managing schedules with the SDK
|
|
|
|
### Retrieving an existing schedule
|
|
|
|
```ts
|
|
const retrievedSchedule = await schedules.retrieve(scheduleId);
|
|
```
|
|
|
|
See [the SDK reference](/management/schedules/retrieve) for full details.
|
|
|
|
### Listing schedules
|
|
|
|
```ts
|
|
const allSchedules = await schedules.list();
|
|
```
|
|
|
|
See [the SDK reference](/management/schedules/list) for full details.
|
|
|
|
### Updating a schedule
|
|
|
|
```ts
|
|
const updatedSchedule = await schedules.update(scheduleId, {
|
|
task: firstScheduledTask.id,
|
|
cron: "0 0 1 * *",
|
|
externalId: "ext_1234444",
|
|
deduplicationKey: "my-deduplication-key",
|
|
});
|
|
```
|
|
|
|
See [the SDK reference](/management/schedules/update) for full details.
|
|
|
|
### Deactivating a schedule
|
|
|
|
```ts
|
|
const deactivatedSchedule = await schedules.deactivate(scheduleId);
|
|
```
|
|
|
|
See [the SDK reference](/management/schedules/deactivate) for full details.
|
|
|
|
### Activating a schedule
|
|
|
|
```ts
|
|
const activatedSchedule = await schedules.activate(scheduleId);
|
|
```
|
|
|
|
See [the SDK reference](/management/schedules/activate) for full details.
|
|
|
|
### Deleting a schedule
|
|
|
|
```ts
|
|
const deletedSchedule = await schedules.del(scheduleId);
|
|
```
|
|
|
|
See [the SDK reference](/management/schedules/delete) for full details.
|
|
|
|
### Getting possible timezones
|
|
|
|
You might want to show a dropdown menu in your UI so your users can select their timezone. You can get a list of all possible timezones using the SDK:
|
|
|
|
```ts
|
|
const timezones = await schedules.timezones();
|
|
```
|
|
|
|
See [the SDK reference](/management/schedules/timezones) for full details.
|