$npx -y skills add jackspace/ClaudeSkillz --skill cloudflare-cron-triggersComplete knowledge domain for Cloudflare Cron Triggers - scheduled execution of Workers using cron expressions for periodic tasks, maintenance jobs, and automated workflows. Use when: scheduling Workers to run periodically, adding cron triggers to Workers, configuring scheduled t
| 1 | # Cloudflare Cron Triggers |
| 2 | |
| 3 | **Status**: Production Ready ✅ |
| 4 | **Last Updated**: 2025-10-23 |
| 5 | **Dependencies**: cloudflare-worker-base (for Worker setup) |
| 6 | **Latest Versions**: wrangler@4.43.0, @cloudflare/workers-types@4.20251014.0 |
| 7 | |
| 8 | --- |
| 9 | |
| 10 | ## Quick Start (5 Minutes) |
| 11 | |
| 12 | ### 1. Add Scheduled Handler to Your Worker |
| 13 | |
| 14 | **src/index.ts:** |
| 15 | |
| 16 | ```typescript |
| 17 | export default { |
| 18 | async scheduled( |
| 19 | controller: ScheduledController, |
| 20 | env: Env, |
| 21 | ctx: ExecutionContext |
| 22 | ): Promise<void> { |
| 23 | console.log('Cron job executed at:', new Date(controller.scheduledTime)); |
| 24 | console.log('Triggered by cron:', controller.cron); |
| 25 | |
| 26 | // Your scheduled task logic here |
| 27 | await doPeriodicTask(env); |
| 28 | }, |
| 29 | }; |
| 30 | ``` |
| 31 | |
| 32 | **Why this matters:** |
| 33 | - Handler must be named exactly `scheduled` (not `scheduledHandler` or `onScheduled`) |
| 34 | - Must be exported in default export object |
| 35 | - Must use ES modules format (not Service Worker format) |
| 36 | |
| 37 | ### 2. Configure Cron Trigger in Wrangler |
| 38 | |
| 39 | **wrangler.jsonc:** |
| 40 | |
| 41 | ```jsonc |
| 42 | { |
| 43 | "name": "my-scheduled-worker", |
| 44 | "main": "src/index.ts", |
| 45 | "compatibility_date": "2025-10-23", |
| 46 | "triggers": { |
| 47 | "crons": [ |
| 48 | "0 * * * *" // Every hour at minute 0 |
| 49 | ] |
| 50 | } |
| 51 | } |
| 52 | ``` |
| 53 | |
| 54 | **CRITICAL:** |
| 55 | - Cron expressions use 5 fields: `minute hour day-of-month month day-of-week` |
| 56 | - All times are **UTC only** (no timezone conversion) |
| 57 | - Changes take **up to 15 minutes** to propagate globally |
| 58 | |
| 59 | ### 3. Test Locally |
| 60 | |
| 61 | ```bash |
| 62 | # Enable scheduled testing |
| 63 | npx wrangler dev --test-scheduled |
| 64 | |
| 65 | # In another terminal, trigger the scheduled handler |
| 66 | curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*" |
| 67 | |
| 68 | # View output in wrangler dev terminal |
| 69 | ``` |
| 70 | |
| 71 | **Testing tips:** |
| 72 | - `/__scheduled` endpoint is only available with `--test-scheduled` flag |
| 73 | - Can pass any cron expression in query parameter |
| 74 | - Python Workers use `/cdn-cgi/handler/scheduled` instead |
| 75 | |
| 76 | ### 4. Deploy |
| 77 | |
| 78 | ```bash |
| 79 | npm run deploy |
| 80 | # or |
| 81 | npx wrangler deploy |
| 82 | ``` |
| 83 | |
| 84 | **After deployment:** |
| 85 | - Changes may take up to 15 minutes to propagate |
| 86 | - Check dashboard: Workers & Pages > [Your Worker] > **Cron Triggers** |
| 87 | - View past executions in **Logs** tab |
| 88 | |
| 89 | --- |
| 90 | |
| 91 | ## Cron Expression Syntax |
| 92 | |
| 93 | ### Five-Field Format |
| 94 | |
| 95 | ``` |
| 96 | * * * * * |
| 97 | │ │ │ │ │ |
| 98 | │ │ │ │ └─── Day of Week (0-6, Sunday=0) |
| 99 | │ │ │ └───── Month (1-12) |
| 100 | │ │ └─────── Day of Month (1-31) |
| 101 | │ └───────── Hour (0-23) |
| 102 | └─────────── Minute (0-59) |
| 103 | ``` |
| 104 | |
| 105 | ### Special Characters |
| 106 | |
| 107 | | Character | Meaning | Example | |
| 108 | |-----------|---------|---------| |
| 109 | | `*` | Every | `* * * * *` = every minute | |
| 110 | | `,` | List | `0,30 * * * *` = every hour at :00 and :30 | |
| 111 | | `-` | Range | `0 9-17 * * *` = every hour from 9am-5pm | |
| 112 | | `/` | Step | `*/15 * * * *` = every 15 minutes | |
| 113 | |
| 114 | ### Common Patterns |
| 115 | |
| 116 | ```bash |
| 117 | # Every minute |
| 118 | * * * * * |
| 119 | |
| 120 | # Every 5 minutes |
| 121 | */5 * * * * |
| 122 | |
| 123 | # Every 15 minutes |
| 124 | */15 * * * * |
| 125 | |
| 126 | # Every hour at minute 0 |
| 127 | 0 * * * * |
| 128 | |
| 129 | # Every hour at minute 30 |
| 130 | 30 * * * * |
| 131 | |
| 132 | # Every 6 hours |
| 133 | 0 */6 * * * |
| 134 | |
| 135 | # Every day at midnight (00:00 UTC) |
| 136 | 0 0 * * * |
| 137 | |
| 138 | # Every day at noon (12:00 UTC) |
| 139 | 0 12 * * * |
| 140 | |
| 141 | # Every day at 3:30am UTC |
| 142 | 30 3 * * * |
| 143 | |
| 144 | # Every Monday at 9am UTC |
| 145 | 0 9 * * 1 |
| 146 | |
| 147 | # Every weekday at 9am UTC |
| 148 | 0 9 * * 1-5 |
| 149 | |
| 150 | # Every Sunday at midnight UTC |
| 151 | 0 0 * * 0 |
| 152 | |
| 153 | # First day of every month at midnight UTC |
| 154 | 0 0 1 * * |
| 155 | |
| 156 | # Twice a day (6am and 6pm UTC) |
| 157 | 0 6,18 * * * |
| 158 | |
| 159 | # Every 30 minutes during business hours (9am-5pm UTC, weekdays) |
| 160 | */30 9-17 * * 1-5 |
| 161 | ``` |
| 162 | |
| 163 | **CRITICAL: UTC Timezone Only** |
| 164 | - All cron triggers execute on **UTC time** |
| 165 | - No timezone conversion available |
| 166 | - Convert your local time to UTC manually |
| 167 | - Example: 9am PST = 5pm UTC (next day during DST) |
| 168 | |
| 169 | --- |
| 170 | |
| 171 | ## ScheduledController Interface |
| 172 | |
| 173 | ```typescript |
| 174 | interface ScheduledController { |
| 175 | readonly cron: string; // The cron expression that triggered this execution |
| 176 | readonly type: string; // Always "scheduled" |
| 177 | readonly scheduledTime: number; // Unix timestamp (ms) when scheduled |
| 178 | } |
| 179 | ``` |
| 180 | |
| 181 | ### Properties |
| 182 | |
| 183 | #### `controller.cron` (string) |
| 184 | |
| 185 | The cron expression that triggered this execution. |
| 186 | |
| 187 | ```typescript |
| 188 | export default { |
| 189 | async scheduled(controller: ScheduledController, env: Env): Promise<void> { |
| 190 | console.log(`Triggered by: ${controller.cron}`); |
| 191 | // Output: "Triggered by: 0 * |