|
| 1 | +--- |
| 2 | +title: Cron |
| 3 | +description: "Scheduling and managing cron jobs in Deno Deploy, including defining cron jobs in code, execution lifecycle, retries, and observability." |
| 4 | +--- |
| 5 | + |
| 6 | +Cron jobs are scheduled tasks that run automatically on a defined schedule. You |
| 7 | +define cron jobs in your code using the `Deno.cron()` API, deploy your |
| 8 | +application, and the platform discovers and runs them on schedule. |
| 9 | + |
| 10 | +## Defining cron jobs in code |
| 11 | + |
| 12 | +`Deno.cron()` takes a human-readable name, a schedule, and a handler function. |
| 13 | +The name identifies the cron job in the dashboard and logs, the schedule |
| 14 | +determines when it fires, and the handler contains the code to run on each |
| 15 | +invocation. The schedule can be either a standard |
| 16 | +[5-field cron expression](https://en.wikipedia.org/wiki/Cron#UNIX-like) or a |
| 17 | +structured object. All times are UTC — this avoids ambiguity around daylight |
| 18 | +saving transitions. See the |
| 19 | +[full API reference](https://docs.deno.com/api/deno/~/Deno.cron) for details. |
| 20 | + |
| 21 | +```typescript |
| 22 | +Deno.cron("cleanup-old-data", "0 * * * *", () => { |
| 23 | + console.log("Cleaning up old data..."); |
| 24 | +}); |
| 25 | + |
| 26 | +Deno.cron( |
| 27 | + "sync-data", |
| 28 | + "*/15 * * * *", |
| 29 | + { |
| 30 | + backoffSchedule: [1000, 5000, 10000], |
| 31 | + }, |
| 32 | + async () => { |
| 33 | + await syncExternalData(); |
| 34 | + }, |
| 35 | +); |
| 36 | +``` |
| 37 | + |
| 38 | +### Common schedule expressions |
| 39 | + |
| 40 | +| Schedule | Expression | |
| 41 | +| ------------------------------ | -------------- | |
| 42 | +| Every minute | `* * * * *` | |
| 43 | +| Every 15 minutes | `*/15 * * * *` | |
| 44 | +| Every hour | `0 * * * *` | |
| 45 | +| Every 3 hours | `0 */3 * * *` | |
| 46 | +| Daily at 1 AM | `0 1 * * *` | |
| 47 | +| Every Wednesday at midnight | `0 0 * * WED` | |
| 48 | +| First of the month at midnight | `0 0 1 * *` | |
| 49 | + |
| 50 | +### Organizing cron declarations |
| 51 | + |
| 52 | +You must register cron jobs at the module top level, before `Deno.serve()` |
| 53 | +starts. The platform extracts `Deno.cron()` definitions at deployment time by |
| 54 | +evaluating your module's top-level code — this is how it discovers which cron |
| 55 | +jobs exist and what their schedules are. Cron jobs registered inside request |
| 56 | +handlers, conditionals, or after the server starts will not be picked up. |
| 57 | + |
| 58 | +As the number of cron jobs grows, keeping them all in your main entrypoint can |
| 59 | +get noisy. A common convention is to define cron handlers in a dedicated file |
| 60 | +and import it at the top of your entrypoint. |
| 61 | + |
| 62 | +```typescript title="crons.ts" |
| 63 | +Deno.cron("cleanup-old-data", "0 * * * *", async () => { |
| 64 | + await deleteExpiredRecords(); |
| 65 | +}); |
| 66 | + |
| 67 | +Deno.cron("sync-data", "*/15 * * * *", async () => { |
| 68 | + await syncExternalData(); |
| 69 | +}); |
| 70 | +``` |
| 71 | + |
| 72 | +```typescript title="main.ts" |
| 73 | +import "./crons.ts"; |
| 74 | + |
| 75 | +Deno.serve(handler); |
| 76 | +``` |
| 77 | + |
| 78 | +Because `Deno.cron()` calls execute at the module top level, the import alone is |
| 79 | +enough to register them — no need to call or re-export anything. |
| 80 | + |
| 81 | +For projects with many cron jobs, you can use a `crons/` directory with one file |
| 82 | +per cron job or group of related cron jobs, and a barrel file that re-exports |
| 83 | +them: |
| 84 | + |
| 85 | +```typescript title="crons/mod.ts" |
| 86 | +import "./cleanup.ts"; |
| 87 | +import "./sync.ts"; |
| 88 | +``` |
| 89 | + |
| 90 | +```typescript title="main.ts" |
| 91 | +import "./crons/mod.ts"; |
| 92 | + |
| 93 | +Deno.serve(handler); |
| 94 | +``` |
| 95 | + |
| 96 | +### Restrictions |
| 97 | + |
| 98 | +There are a couple of restrictions to keep in mind when defining cron jobs. The |
| 99 | +name passed to `Deno.cron()` can be at most **256 characters** long — this is |
| 100 | +the identifier that appears in the dashboard and logs, so keeping it concise is |
| 101 | +good practice regardless. On free organizations, each revision can register at |
| 102 | +most **10 cron jobs**. If your project needs more, upgrading to a paid plan |
| 103 | +removes this limit. |
| 104 | + |
| 105 | +## Execution lifecycle & status |
| 106 | + |
| 107 | +When a cron job is due, it fires independently on each timeline where it's |
| 108 | +registered. Each execution runs the handler from that timeline's active |
| 109 | +revision. For example, if a `cleanup-old-data` cron job is registered in both |
| 110 | +the production timeline and a `staging` branch timeline, the production |
| 111 | +execution runs the handler from the production revision, and the staging |
| 112 | +execution runs the handler from the staging revision. Each execution will be |
| 113 | +billed as one inbound HTTP request. |
| 114 | + |
| 115 | +Cron job executions progress through these statuses: |
| 116 | + |
| 117 | +| Status | Color | Description | |
| 118 | +| ----------- | ------ | --------------------------------------- | |
| 119 | +| **Running** | Yellow | Cron job handler is currently executing | |
| 120 | +| **OK** | Green | Completed successfully | |
| 121 | +| **Error** | Red | Failed — hover to see the error message | |
| 122 | + |
| 123 | +The platform prevents overlapping executions: the same cron job cannot run |
| 124 | +concurrently. If a cron job is still running when the next scheduled invocation |
| 125 | +is due, that invocation is skipped. This avoids resource contention and ensures |
| 126 | +each execution can complete without interference. |
| 127 | + |
| 128 | +## Retries & backoff |
| 129 | + |
| 130 | +By default, failed cron job executions are not retried. You can optionally |
| 131 | +provide a `backoffSchedule` array to enable retries and control exactly when |
| 132 | +each one happens. |
| 133 | + |
| 134 | +The `backoffSchedule` is an array of delays in milliseconds, where each element |
| 135 | +specifies how long to wait before the next retry attempt: |
| 136 | + |
| 137 | +- Maximum **5 retries** per execution |
| 138 | +- Maximum delay per retry: **1 hour** (3,600,000 ms) |
| 139 | +- Retries do not affect the cron job schedule — the next scheduled run will be |
| 140 | + executed on time, even if the previous run has retries pending. If a retry and |
| 141 | + the next scheduled run coincide, the later one will be skipped as per the |
| 142 | + overlapping policy described above. |
| 143 | + |
| 144 | +Example: `backoffSchedule: [1000, 5000, 10000]` retries up to 3 times with |
| 145 | +delays of 1s, 5s, and 10s. |
| 146 | + |
| 147 | +## Dashboard |
| 148 | + |
| 149 | +The **Cron** tab in the app sidebar gives you an overview of all registered cron |
| 150 | +jobs across your project. Each entry shows the cron job's schedule, its most |
| 151 | +recent executions, and the active [timelines](/deploy/reference/timelines/) it |
| 152 | +belongs to. When a cron job with the same name is registered with different |
| 153 | +schedules on different timelines, each distinct schedule appears as its own |
| 154 | +entry so you can track them independently. |
| 155 | + |
| 156 | +Click "View Details" on any cron job to open its detail page, which shows the |
| 157 | +full execution history. You can filter executions using the search bar: |
| 158 | + |
| 159 | +- **`status:<value>`** — filter by status (`ok`, `error`, `running`) |
| 160 | +- **`timeline:<name>`** — filter by timeline name (substring match, |
| 161 | + case-insensitive) |
| 162 | + |
| 163 | +## Observability integration |
| 164 | + |
| 165 | +Cron job executions produce OpenTelemetry traces. Click "View Trace" in the |
| 166 | +execution history to navigate to the |
| 167 | +[Observability](/deploy/reference/observability/) traces page for that specific |
| 168 | +trace. |
| 169 | + |
| 170 | +On the traces page, you can use these cron-related filters: |
| 171 | + |
| 172 | +- **`kind:cron`** — show only cron spans |
| 173 | +- **`cron.name:<name>`** — filter by cron name |
| 174 | +- **`cron.schedule:<schedule>`** — filter by schedule expression |
| 175 | + |
| 176 | +## Timelines |
| 177 | + |
| 178 | +Cron jobs run on production and git branch |
| 179 | +[timelines](/deploy/reference/timelines/). The platform extracts `Deno.cron()` |
| 180 | +definitions at deployment time and schedules them for execution, so each |
| 181 | +timeline runs the set of cron jobs defined in its active revision's code. To |
| 182 | +add, remove, or modify cron jobs, update your code and deploy a new revision. |
| 183 | +Rolling back to a previous deployment re-registers the cron jobs from that |
| 184 | +deployment. You can see which cron jobs are currently registered in a given |
| 185 | +timeline from its page in the dashboard. |
0 commit comments