Workers Cron Triggers -- Scheduled Tasks
In this tutorial, you will learn how to use Cloudflare Workers Cron Triggers to run code on a schedule without maintaining any server infrastructure. Scheduled tasks are important because many backend operations -- daily report generation, cache warming, database cleanup, and API polling -- must run at specific intervals. A real-world example is an analytics platform that aggregates hourly pageview counts from KV stores and writes summaries to D1 every hour.
How Cron Triggers Work
A Cron Trigger is a Worker that runs on a schedule defined by a cron expression. Instead of listening for HTTP requests, the Worker's scheduled handler is invoked at the specified times. The Worker runs in the same runtime environment as request-driven Workers, with access to all the same bindings (KV, D1, R2, Queues, environment variables). The key difference is that there is no incoming Request object -- instead, the handler receives a ScheduledEvent with a cron string and a scheduledTime timestamp.
Cron Trigger Architecture
flowchart TD
C[Cron Expression */15 * * * *] --> T[Cloudflare Scheduler]
T --> W[Worker scheduled handler]
W --> B[Bindings KV / D1 / R2]
W --> L[Logging to Tail Worker]
W --> R[Result stored or sent]
subgraph Schedule
M1[Minute]
H[Hour]
D[Day]
M[Month]
WK[Weekday]
end
style C fill:#f90,color:#fff
style W fill:#3498db,color:#fff
The scheduler invokes the Worker at each matching time. Workers have a maximum execution duration depending on your plan: 30 seconds on the free plan and 15 minutes on the paid plan. Cron Triggers do not retry on failure automatically.
Defining a Cron Trigger
// wrangler.toml
// [triggers]
// crons = ["0 */6 * * *", "0 0 * * 0"]
// worker.js -- scheduled handler
export default {
async scheduled(event, env, ctx) {
// event.cron: the cron expression that triggered this run
// event.scheduledTime: the Unix timestamp of the scheduled time
const { results } = await env.DB.prepare(
'SELECT COUNT(*) AS total FROM sessions WHERE expires_at < datetime("now")'
).first();
if (results.total > 0) {
await env.DB.prepare(
'DELETE FROM sessions WHERE expires_at < datetime("now")'
).run();
}
console.log(`Cleaned up ${results.total} expired sessions at ${new Date(event.scheduledTime).toISOString()}`);
}
};
// Expected output in logs:
// Cleaned up 47 expired sessions at 2026-06-23T06:00:00.000Z
The scheduled handler does not return a Response. Output is written to logs or to external storage. The crons array in wrangler.toml can contain multiple cron expressions.
Email Report Generation
// Daily sales report using D1 + email via API
export default {
async scheduled(event, env, ctx) {
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
const { results } = await env.DB.prepare(
`SELECT product, SUM(amount) AS total, COUNT(*) AS orders
FROM sales
WHERE DATE(created_at) = ?1
GROUP BY product
ORDER BY total DESC`
).bind(yesterday).all();
const report = {
date: yesterday,
totalRevenue: results.reduce((s, r) => s + r.total, 0),
topProducts: results.slice(0, 5)
};
await env.REPORT_BUCKET.put(
`reports/sales-${yesterday}.json`,
JSON.stringify(report, null, 2)
);
console.log(`Sales report generated: $${report.totalRevenue} revenue on ${yesterday}`);
}
};
// Expected output in R2 bucket: reports/sales-2026-06-22.json
// Expected log:
// Sales report generated: $12450.50 revenue on 2026-06-22
This Worker runs daily at midnight, aggregates the previous day's sales from D1, and writes a JSON report to R2. The report can be served via public bucket or processed further.
Cache Warming with KV
// Preload popular content into KV every 5 minutes
export default {
async scheduled(event, env, ctx) {
const popularPaths = [
'/api/posts',
'/api/categories',
'/api/featured'
];
const results = [];
for (const path of popularPaths) {
const start = Date.now();
const response = await fetch(new Request(`https://api.example.com${path}`));
const data = await response.text();
const duration = Date.now() - start;
// Cache in KV with 1-hour TTL
await env.CACHE.put(
`response:${path}`,
data,
{ expirationTtl: 3600 }
);
results.push({ path, status: response.status, duration });
}
console.log(`Cache warmed: ${JSON.stringify(results)}`);
}
};
// Expected output in logs:
// Cache warmed: [{"path":"/api/posts","status":200,"duration":120},{"path":"/api/categories","status":200,"duration":95},{"path":"/api/featured","status":200,"duration":110}]
Cache warming runs on a 5-minute schedule, fetching popular API endpoints and storing the results in KV. When users request these endpoints, the Worker serves the pre-warmed cache instead of hitting the origin.
Common Errors and Troubleshooting
Cron Expression Invalid
Cron expressions must have five fields: minute, hour, day-of-month, month, day-of-week. Six-field expressions (with seconds) are not supported. Validate your cron expression with an online tool before deploying.
Execution Timeout
Workers on the free plan timeout after 30 seconds. Use ctx.waitUntil() to continue background tasks after the handler returns, but the total duration including waitUntil tasks cannot exceed the plan limit.
Scheduled Handler Not Found
If the Worker exports a fetch handler but no scheduled handler, the cron trigger runs with no effect. The Worker module must export a scheduled function for cron triggers to have any purpose.
Duplicate Executions
Cron Triggers guarantee at-least-once delivery. Under rare circumstances, the same trigger may fire twice. Design your scheduled Workers to be idempotent -- running the same job twice should produce the same result as running it once.
Local Testing
Wrangler does not natively trigger cron schedules locally. Use wrangler dev --test-scheduled to manually invoke the scheduled handler with a GET request to __scheduled.
Practice Questions
- What function must a Worker export to handle cron triggers?
- What is the maximum execution time for a cron-triggered Worker on the free plan?
- How do you define cron schedules in wrangler.toml?
FAQ
Summary
Workers Cron Triggers let you run code on a schedule using standard cron expressions. The scheduled handler receives a ScheduledEvent with the cron expression and scheduled time. Use cron triggers for session cleanup, report generation, cache warming, data synchronization, and any other time-based Background Jobs. Design Workers to be idempotent since triggers may fire more than once. Cron Triggers combined with D1, KV, and R2 enable building complete Serverless Data Pipelines at the edge.
This guide is brought to you by the developers of Cloudflare, Cloudflare Workers, and Durga Antivirus Pro at DodaTech.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro