Cloudflare Cron Triggers
Status: Production Ready ✅ Last Updated: 2025-11-25 Dependencies: cloudflare-worker-base (for Worker setup) Latest Versions: wrangler@4.50.0, @cloudflare/workers-types@4.20251125.0
Quick Start (5 Minutes)
1. Add Scheduled Handler to Your Worker
src/index.ts:
export default {
async scheduled(
controller: ScheduledController,
env: Env,
ctx: ExecutionContext
): Promise<void> {
console.log('Cron job executed at:', new Date(controller.scheduledTime));
console.log('Triggered by cron:', controller.cron);
// Your scheduled task logic here
await doPeriodicTask(env);
},
};
Why this matters:
- Handler must be named exactly
scheduled(notscheduledHandleroronScheduled) - Must be exported in default export object
- Must use ES modules format (not Service Worker format)
2. Configure Cron Trigger in Wrangler
wrangler.jsonc:
{
"name": "my-scheduled-worker",
"main": "src/index.ts",
"compatibility_date": "2025-10-23",
"triggers": {
"crons": [
"0 * * * *" // Every hour at minute 0
]
}
}
CRITICAL:
- Cron expressions use 5 fields:
minute hour day-of-month month day-of-week - All times are UTC only (no timezone conversion)
- Changes take up to 15 minutes to propagate globally
3. Test Locally
# Enable scheduled testing
bunx wrangler dev --test-scheduled
# In another terminal, trigger the scheduled handler
curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"
# View output in wrangler dev terminal
Testing tips:
/__scheduledendpoint is only available with--test-scheduledflag- Can pass any cron expression in query parameter
- Python Workers use
/cdn-cgi/handler/scheduledinstead
4. Deploy
npm run deploy
# or
bunx wrangler deploy
After deployment:
- Changes may take up to 15 minutes to propagate
- Check dashboard: Workers & Pages > [Your Worker] > Cron Triggers
- View past executions in Logs tab
When to Load References
Load immediately when user mentions:
cron-expressions-reference.md→ "cron syntax", "schedule format", "expression", "minute hour day", "every X minutes"common-patterns.md→ "examples", "use cases", "patterns", "real-world", "database cleanup", "report generation", "how to"integration-patterns.md→ "implement", "Hono", "multiple triggers", "bindings", "workflows", "error handling"wrangler-config.md→ "configuration", "wrangler.jsonc", "multiple crons", "environment-specific", "dev staging production"testing-guide.md→ "test", "local development", "__scheduled", "unit test", "curl", "debugging"
Load proactively when:
- Building new scheduled task → Load
integration-patterns.md - Configuring wrangler.jsonc → Load
wrangler-config.md - Debugging cron expression → Load
cron-expressions-reference.md - Testing locally → Load
testing-guide.md - Looking for examples → Load
common-patterns.md
Cron Expression Syntax
Five-Field Format
* * * * *
│ │ │ │ │
│ │ │ │ └─── Day of Week (0-6, Sunday=0)
│ │ │ └───── Month (1-12)
│ │ └─────── Day of Month (1-31)
│ └───────── Hour (0-23)
└─────────── Minute (0-59)
Special Characters
| Character | Meaning | Example |
|---|---|---|
* | Every | * * * * * = every minute |
, | List | 0,30 * * * * = every hour at :00 and :30 |
- | Range | 0 9-17 * * * = every hour from 9am-5pm |
/ | Step | */15 * * * * = every 15 minutes |
Common Patterns
# Every minute
* * * * *
# Every 5 minutes
*/5 * * * *
# Every 15 minutes
*/15 * * * *
# Every hour at minute 0
0 * * * *
# Every hour at minute 30
30 * * * *
# Every 6 hours
0 */6 * * *
# Every day at midnight (00:00 UTC)
0 0 * * *
# Every day at noon (12:00 UTC)
0 12 * * *
# Every day at 3:30am UTC
30 3 * * *
# Every Monday at 9am UTC
0 9 * * 1
# Every weekday at 9am UTC
0 9 * * 1-5
# Every Sunday at midnight UTC
0 0 * * 0
# First day of every month at midnight UTC
0 0 1 * *
# Twice a day (6am and 6pm UTC)
0 6,18 * * *
# Every 30 minutes during business hours (9am-5pm UTC, weekdays)
*/30 9-17 * * 1-5
CRITICAL: UTC Timezone Only
- All cron triggers execute on UTC time
- No timezone conversion available
- Convert your local time to UTC manually
- Example: 9am PST = 5pm UTC (next day during DST)
ScheduledController Interface
interface ScheduledController {
readonly cron: string; // The cron expression that triggered this execution
readonly type: string; // Always "scheduled"
readonly scheduledTime: number; // Unix timestamp (ms) when scheduled
}
Properties
controller.cron (string)
The cron expression that triggered this execution.
export default {
async scheduled(controller: ScheduledController, env: Env): Promise<void> {
console.log(`Triggered by: ${controller.cron}`);
// Output: "Triggered by: 0 * * * *"
},
};
Use case: Differentiate between multiple cron schedules (see Multiple Cron Triggers pattern).
controller.type (string)
Always returns "scheduled" for cron-triggered executions.
if (controller.type === 'scheduled') {
// This is a cron-triggered execution
}
controller.scheduledTime (number)
Unix timestamp (milliseconds since epoch) when this execution was scheduled to run.
export default {
async scheduled(controller: ScheduledController): Promise<void> {
const scheduledDate = new Date(controller.scheduledTime);
console.log(`Scheduled for: ${scheduledDate.toISOString()}`);
// Output: "Scheduled for: 2025-10-23T15:00:00.000Z"
},
};
Note: This is the scheduled time, not the actual execution time. Due to system load, actual execution may be slightly delayed (usually <1 second).
Execution Context
export default {
async scheduled(
controller: ScheduledController,
env: Env,
ctx: ExecutionContext // ← Execution context
): Promise<void> {
// Use ctx.waitUntil() for async operations that should complete
ctx.waitUntil(logToAnalytics(env));
},
};
ctx.waitUntil(promise: Promise<any>)
Extends the execution context to wait for async operations to complete after the handler returns.
Use cases:
- Logging to external services
- Analytics tracking
- Cleanup operations
- Non-critical background tasks
export default {
async scheduled(controller: ScheduledController, env: Env, ctx: ExecutionContext): Promise<void> {
// Critical task - must complete before handler exits
await processData(env);
// Non-critical tasks - can complete in background
ctx.waitUntil(sendMetrics(env));
ctx.waitUntil(cleanupOldData(env));
ctx.waitUntil(notifySlack({ message: 'Cron completed' }));
},
};
Important: First waitUntil() that fails will be reported as the status in dashboard logs.
Integration Patterns
6 production-ready cron patterns:
- Standalone Worker with Cron - Single scheduled function for background tasks (database cleanup, report generation)
- Hono + Cron Combination - HTTP endpoints + scheduled tasks in one Worker, sharing bindings and reducing costs
- Multiple Cron Triggers - Different schedules for different tasks using
controller.cronto route execution - Accessing Bindings - Use D1, KV, R2, AI, Vectorize, Queues, Workflows, Durable Objects in scheduled functions
- Integrating with Workflows - Trigger complex, long-running multi-step workflows on schedule
- Error Handling Best Practices - Comprehensive error handling with retry logic, alerting (Slack/email), failure logging, and monitoring
Load references/integration-patterns.md for complete implementations with code examples, configuration details, and best practices.